|
|
@ -145,7 +145,7 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
lock (_activeTranscodingJobs)
|
|
|
|
lock (_activeTranscodingJobs)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// This is really only needed for HLS.
|
|
|
|
// This is really only needed for HLS.
|
|
|
|
// Progressive streams can stop on their own reliably
|
|
|
|
// Progressive streams can stop on their own reliably.
|
|
|
|
jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -241,7 +241,7 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
lock (_activeTranscodingJobs)
|
|
|
|
lock (_activeTranscodingJobs)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// This is really only needed for HLS.
|
|
|
|
// This is really only needed for HLS.
|
|
|
|
// Progressive streams can stop on their own reliably
|
|
|
|
// Progressive streams can stop on their own reliably.
|
|
|
|
jobs.AddRange(_activeTranscodingJobs.Where(killJob));
|
|
|
|
jobs.AddRange(_activeTranscodingJobs.Where(killJob));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -304,10 +304,10 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
|
|
|
|
|
|
|
|
process!.StandardInput.WriteLine("q");
|
|
|
|
process!.StandardInput.WriteLine("q");
|
|
|
|
|
|
|
|
|
|
|
|
// Need to wait because killing is asynchronous
|
|
|
|
// Need to wait because killing is asynchronous.
|
|
|
|
if (!process.WaitForExit(5000))
|
|
|
|
if (!process.WaitForExit(5000))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_logger.LogInformation("Killing ffmpeg process for {Path}", job.Path);
|
|
|
|
_logger.LogInformation("Killing FFmpeg process for {Path}", job.Path);
|
|
|
|
process.Kill();
|
|
|
|
process.Kill();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -470,11 +470,11 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// <summary>
|
|
|
|
/// Starts the FFMPEG.
|
|
|
|
/// Starts FFmpeg.
|
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
/// <param name="state">The state.</param>
|
|
|
|
/// <param name="outputPath">The output path.</param>
|
|
|
|
/// <param name="outputPath">The output path.</param>
|
|
|
|
/// <param name="commandLineArguments">The command line arguments for ffmpeg.</param>
|
|
|
|
/// <param name="commandLineArguments">The command line arguments for FFmpeg.</param>
|
|
|
|
/// <param name="request">The <see cref="HttpRequest"/>.</param>
|
|
|
|
/// <param name="request">The <see cref="HttpRequest"/>.</param>
|
|
|
|
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
|
|
|
|
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
|
|
|
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
|
|
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
|
|
@ -501,13 +501,13 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
{
|
|
|
|
{
|
|
|
|
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
|
|
|
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
|
|
|
|
|
|
|
|
|
|
|
throw new ArgumentException("User does not have access to video transcoding");
|
|
|
|
throw new ArgumentException("User does not have access to video transcoding.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(_mediaEncoder.EncoderPath))
|
|
|
|
if (string.IsNullOrEmpty(_mediaEncoder.EncoderPath))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
throw new ArgumentException("FFMPEG path not set.");
|
|
|
|
throw new ArgumentException("FFmpeg path not set.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var process = new Process
|
|
|
|
var process = new Process
|
|
|
@ -544,18 +544,20 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
|
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
|
|
_logger.LogInformation(commandLineLogMessage);
|
|
|
|
_logger.LogInformation(commandLineLogMessage);
|
|
|
|
|
|
|
|
|
|
|
|
var logFilePrefix = "ffmpeg-transcode";
|
|
|
|
var logFilePrefix = "FFmpeg.Transcode-";
|
|
|
|
if (state.VideoRequest != null
|
|
|
|
if (state.VideoRequest != null
|
|
|
|
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
|
|
|
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec)
|
|
|
|
logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec)
|
|
|
|
? "ffmpeg-remux"
|
|
|
|
? "FFmpeg.Remux-"
|
|
|
|
: "ffmpeg-directstream";
|
|
|
|
: "FFmpeg.DirectStream-";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var logFilePath = Path.Combine(_serverConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
|
|
|
var logFilePath = Path.Combine(
|
|
|
|
|
|
|
|
_serverConfigurationManager.ApplicationPaths.LogDirectoryPath,
|
|
|
|
|
|
|
|
$"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log");
|
|
|
|
|
|
|
|
|
|
|
|
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
|
|
|
// FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
|
|
|
Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
|
|
|
Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
|
|
|
|
|
|
|
|
|
|
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
|
|
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
|
|
@ -569,17 +571,17 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_logger.LogError(ex, "Error starting ffmpeg");
|
|
|
|
_logger.LogError(ex, "Error starting FFmpeg");
|
|
|
|
|
|
|
|
|
|
|
|
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
|
|
|
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
|
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("Launched ffmpeg process");
|
|
|
|
_logger.LogDebug("Launched FFmpeg process");
|
|
|
|
state.TranscodingJob = transcodingJob;
|
|
|
|
state.TranscodingJob = transcodingJob;
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
_ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
|
|
|
|
_ = new JobLogger(_logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for the file to exist before proceeding
|
|
|
|
// Wait for the file to exist before proceeding
|
|
|
@ -748,11 +750,11 @@ namespace Jellyfin.Api.Helpers
|
|
|
|
|
|
|
|
|
|
|
|
if (process.ExitCode == 0)
|
|
|
|
if (process.ExitCode == 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_logger.LogInformation("FFMpeg exited with code 0");
|
|
|
|
_logger.LogInformation("FFmpeg exited with code 0");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
|
|
|
|
_logger.LogError("FFmpeg exited with code {0}", process.ExitCode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
process.Dispose();
|
|
|
|
process.Dispose();
|
|
|
|