|
|
@ -199,82 +199,83 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
|
|
|
|
|
|
|
await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
var processWrapper = new ProcessWrapper(process, this);
|
|
|
|
using (var processWrapper = new ProcessWrapper(process, this))
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_ffProbeResourcePool.Release();
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_ffProbeResourcePool.Release();
|
|
|
|
|
|
|
|
|
|
|
|
_logger.ErrorException("Error starting ffprobe", ex);
|
|
|
|
_logger.ErrorException("Error starting ffprobe", ex);
|
|
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
try
|
|
|
|
{
|
|
|
|
{
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
|
|
|
|
|
|
|
|
var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
|
|
|
|
var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
|
|
|
|
|
|
|
|
|
|
|
|
if (result != null)
|
|
|
|
if (result != null)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (result.streams != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Normalize aspect ratio if invalid
|
|
|
|
if (result.streams != null)
|
|
|
|
foreach (var stream in result.streams)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
|
|
|
|
// Normalize aspect ratio if invalid
|
|
|
|
{
|
|
|
|
foreach (var stream in result.streams)
|
|
|
|
stream.display_aspect_ratio = string.Empty;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
stream.sample_aspect_ratio = string.Empty;
|
|
|
|
if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
stream.display_aspect_ratio = string.Empty;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
stream.sample_aspect_ratio = string.Empty;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
|
|
|
var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
|
|
|
|
|
|
|
|
|
|
|
if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue)
|
|
|
|
if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue)
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var stream in mediaInfo.MediaStreams)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (stream.Type == MediaStreamType.Video && string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
|
|
|
|
foreach (var stream in mediaInfo.MediaStreams)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
try
|
|
|
|
if (stream.Type == MediaStreamType.Video && string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
|
|
|
|
{
|
|
|
|
|
|
|
|
//stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken)
|
|
|
|
|
|
|
|
// .ConfigureAwait(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_logger.ErrorException("Error getting key frame interval", ex);
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
//stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken)
|
|
|
|
|
|
|
|
// .ConfigureAwait(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
_logger.ErrorException("Error getting key frame interval", ex);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return mediaInfo;
|
|
|
|
return mediaInfo;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
{
|
|
|
|
StopProcess(processWrapper, 100, true);
|
|
|
|
StopProcess(processWrapper, 100, true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_ffProbeResourcePool.Release();
|
|
|
|
_ffProbeResourcePool.Release();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath));
|
|
|
|
throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath));
|
|
|
@ -307,31 +308,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
|
|
|
|
|
|
|
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
|
|
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
|
|
|
|
|
|
|
|
|
|
var processWrapper = new ProcessWrapper(process, this);
|
|
|
|
using (var processWrapper = new ProcessWrapper(process, this))
|
|
|
|
|
|
|
|
{
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
|
|
|
|
|
|
|
|
var lines = new List<int>();
|
|
|
|
var lines = new List<int>();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
try
|
|
|
|
{
|
|
|
|
{
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
|
|
|
|
|
|
|
|
await StartReadingOutput(process.StandardOutput.BaseStream, lines, 120000, cancellationToken).ConfigureAwait(false);
|
|
|
|
await StartReadingOutput(process.StandardOutput.BaseStream, lines, 120000, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
throw;
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
finally
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StopProcess(processWrapper, 100, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
finally
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StopProcess(processWrapper, 100, true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
return lines;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task StartReadingOutput(Stream source, List<int> lines, int timeoutMs, CancellationToken cancellationToken)
|
|
|
|
private async Task StartReadingOutput(Stream source, List<int> lines, int timeoutMs, CancellationToken cancellationToken)
|
|
|
@ -490,51 +492,53 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
|
|
|
|
|
|
|
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
var processWrapper = new ProcessWrapper(process, this);
|
|
|
|
using (var processWrapper = new ProcessWrapper(process, this))
|
|
|
|
bool ranToCompletion;
|
|
|
|
{
|
|
|
|
|
|
|
|
bool ranToCompletion;
|
|
|
|
|
|
|
|
|
|
|
|
var memoryStream = new MemoryStream();
|
|
|
|
var memoryStream = new MemoryStream();
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
try
|
|
|
|
{
|
|
|
|
{
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
|
|
|
|
|
|
|
|
#pragma warning disable 4014
|
|
|
|
#pragma warning disable 4014
|
|
|
|
// 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
|
|
|
|
process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
|
|
|
|
process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
|
|
|
|
#pragma warning restore 4014
|
|
|
|
#pragma warning restore 4014
|
|
|
|
|
|
|
|
|
|
|
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
|
|
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
process.BeginErrorReadLine();
|
|
|
|
|
|
|
|
|
|
|
|
ranToCompletion = process.WaitForExit(10000);
|
|
|
|
ranToCompletion = process.WaitForExit(10000);
|
|
|
|
|
|
|
|
|
|
|
|
if (!ranToCompletion)
|
|
|
|
if (!ranToCompletion)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StopProcess(processWrapper, 1000, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
{
|
|
|
|
StopProcess(processWrapper, 1000, false);
|
|
|
|
resourcePool.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
|
|
|
finally
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
resourcePool.Release();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
|
|
|
if (exitCode == -1 || memoryStream.Length == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
memoryStream.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
if (exitCode == -1 || memoryStream.Length == 0)
|
|
|
|
var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
|
|
|
|
{
|
|
|
|
|
|
|
|
memoryStream.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
|
|
|
|
_logger.Error(msg);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.Error(msg);
|
|
|
|
throw new ApplicationException(msg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new ApplicationException(msg);
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
return memoryStream;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
return memoryStream;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string GetTimeParameter(long ticks)
|
|
|
|
public string GetTimeParameter(long ticks)
|
|
|
@ -603,55 +607,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
|
|
|
|
|
|
|
bool ranToCompletion = false;
|
|
|
|
bool ranToCompletion = false;
|
|
|
|
|
|
|
|
|
|
|
|
var processWrapper = new ProcessWrapper(process, this);
|
|
|
|
using (var processWrapper = new ProcessWrapper(process, this))
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
try
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
StartProcess(processWrapper);
|
|
|
|
|
|
|
|
|
|
|
|
// Need to give ffmpeg enough time to make all the thumbnails, which could be a while,
|
|
|
|
// Need to give ffmpeg enough time to make all the thumbnails, which could be a while,
|
|
|
|
// but we still need to detect if the process hangs.
|
|
|
|
// but we still need to detect if the process hangs.
|
|
|
|
// Making the assumption that as long as new jpegs are showing up, everything is good.
|
|
|
|
// Making the assumption that as long as new jpegs are showing up, everything is good.
|
|
|
|
|
|
|
|
|
|
|
|
bool isResponsive = true;
|
|
|
|
bool isResponsive = true;
|
|
|
|
int lastCount = 0;
|
|
|
|
int lastCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
while (isResponsive)
|
|
|
|
while (isResponsive)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (process.WaitForExit(30000))
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
ranToCompletion = true;
|
|
|
|
if (process.WaitForExit(30000))
|
|
|
|
break;
|
|
|
|
{
|
|
|
|
}
|
|
|
|
ranToCompletion = true;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
var jpegCount = Directory.GetFiles(targetDirectory)
|
|
|
|
|
|
|
|
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
|
|
|
|
|
|
var jpegCount = Directory.GetFiles(targetDirectory)
|
|
|
|
isResponsive = (jpegCount > lastCount);
|
|
|
|
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
|
|
|
|
lastCount = jpegCount;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isResponsive = (jpegCount > lastCount);
|
|
|
|
if (!ranToCompletion)
|
|
|
|
lastCount = jpegCount;
|
|
|
|
{
|
|
|
|
|
|
|
|
StopProcess(processWrapper, 1000, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
finally
|
|
|
|
if (!ranToCompletion)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
StopProcess(processWrapper, 1000, false);
|
|
|
|
resourcePool.Release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
finally
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
resourcePool.Release();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
|
|
|
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
|
|
|
|
|
|
|
|
|
|
|
if (exitCode == -1)
|
|
|
|
if (exitCode == -1)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
|
|
|
|
var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
|
|
|
|
|
|
|
|
|
|
|
|
_logger.Error(msg);
|
|
|
|
_logger.Error(msg);
|
|
|
|
|
|
|
|
|
|
|
|
throw new ApplicationException(msg);
|
|
|
|
throw new ApplicationException(msg);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -781,7 +786,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class ProcessWrapper
|
|
|
|
private class ProcessWrapper : IDisposable
|
|
|
|
{
|
|
|
|
{
|
|
|
|
public readonly Process Process;
|
|
|
|
public readonly Process Process;
|
|
|
|
public bool HasExited;
|
|
|
|
public bool HasExited;
|
|
|
@ -810,6 +815,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
|
|
|
|
|
|
|
process.Dispose();
|
|
|
|
process.Dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool _disposed;
|
|
|
|
|
|
|
|
private readonly object _syncLock = new object();
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
lock (_syncLock)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (!_disposed)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (Process != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Process.Exited -= Process_Exited;
|
|
|
|
|
|
|
|
Process.Dispose();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_disposed = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|