Fixed stdout/stderr deadlock issue that was causing ffmpeg to hang when working with large files.
This commit is contained in:
parent
e0089349e1
commit
bae04374e5
|
@ -13,45 +13,6 @@ namespace MediaBrowser.Api
|
|||
/// </summary>
|
||||
public static class ApiService
|
||||
{
|
||||
private static string _FFMpegDirectory = null;
|
||||
/// <summary>
|
||||
/// Gets the folder path to ffmpeg
|
||||
/// </summary>
|
||||
public static string FFMpegDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FFMpegDirectory == null)
|
||||
{
|
||||
_FFMpegDirectory = System.IO.Path.Combine(ApplicationPaths.ProgramDataPath, "ffmpeg");
|
||||
|
||||
if (!Directory.Exists(_FFMpegDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_FFMpegDirectory);
|
||||
|
||||
// Extract ffmpeg
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.ffmpeg.ffmpeg.exe"))
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(FFMpegPath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _FFMpegDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
public static string FFMpegPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.Combine(FFMpegDirectory, "ffmpeg.exe");
|
||||
}
|
||||
}
|
||||
|
||||
public static BaseItem GetItemById(string id)
|
||||
{
|
||||
Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id);
|
||||
|
@ -138,5 +99,58 @@ namespace MediaBrowser.Api
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string _FFMpegDirectory = null;
|
||||
/// <summary>
|
||||
/// Gets the folder path to ffmpeg
|
||||
/// </summary>
|
||||
public static string FFMpegDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FFMpegDirectory == null)
|
||||
{
|
||||
_FFMpegDirectory = System.IO.Path.Combine(ApplicationPaths.ProgramDataPath, "ffmpeg");
|
||||
|
||||
if (!Directory.Exists(_FFMpegDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_FFMpegDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
return _FFMpegDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
private static string _FFMpegPath = null;
|
||||
/// <summary>
|
||||
/// Gets the path to ffmpeg.exe
|
||||
/// </summary>
|
||||
public static string FFMpegPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_FFMpegPath == null)
|
||||
{
|
||||
string filename = "ffmpeg.exe";
|
||||
|
||||
_FFMpegPath = Path.Combine(FFMpegDirectory, filename);
|
||||
|
||||
if (!File.Exists(_FFMpegPath))
|
||||
{
|
||||
// Extract ffprobe
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.FFMpeg." + filename))
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(_FFMpegPath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _FFMpegPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ namespace MediaBrowser.Api.HttpHandlers
|
|||
/// <summary>
|
||||
/// Creates arguments to pass to ffmpeg
|
||||
/// </summary>
|
||||
private string GetAudioArguments()
|
||||
protected override string GetCommandLineArguments()
|
||||
{
|
||||
List<string> audioTranscodeParams = new List<string>();
|
||||
|
||||
|
@ -132,40 +132,6 @@ namespace MediaBrowser.Api.HttpHandlers
|
|||
|
||||
return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -";
|
||||
}
|
||||
|
||||
protected async override Task WriteResponseToOutputStream(Stream stream)
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo();
|
||||
|
||||
startInfo.CreateNoWindow = true;
|
||||
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
|
||||
startInfo.FileName = ApiService.FFMpegPath;
|
||||
startInfo.WorkingDirectory = ApiService.FFMpegDirectory;
|
||||
startInfo.Arguments = GetAudioArguments();
|
||||
|
||||
Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
|
||||
|
||||
Process process = new Process();
|
||||
process.StartInfo = startInfo;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
await process.StandardOutput.BaseStream.CopyToAsync(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BaseMediaHandler<T> : BaseHandler
|
||||
|
@ -252,7 +218,48 @@ namespace MediaBrowser.Api.HttpHandlers
|
|||
base.ProcessRequest(ctx);
|
||||
}
|
||||
|
||||
protected abstract string GetCommandLineArguments();
|
||||
protected abstract string GetOutputFormat();
|
||||
protected abstract bool RequiresConversion();
|
||||
|
||||
protected async override Task WriteResponseToOutputStream(Stream stream)
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo();
|
||||
|
||||
startInfo.CreateNoWindow = true;
|
||||
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.RedirectStandardOutput = true;
|
||||
startInfo.RedirectStandardError = true;
|
||||
|
||||
startInfo.FileName = ApiService.FFMpegPath;
|
||||
startInfo.WorkingDirectory = ApiService.FFMpegDirectory;
|
||||
startInfo.Arguments = GetCommandLineArguments();
|
||||
|
||||
Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
|
||||
|
||||
Process process = new Process();
|
||||
process.StartInfo = startInfo;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await process.StandardOutput.BaseStream.CopyToAsync(stream);
|
||||
|
||||
process.WaitForExit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Logging;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Net.Handlers;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Api.HttpHandlers
|
||||
{
|
||||
class VideoHandler : BaseMediaHandler<Video>
|
||||
{
|
||||
private IEnumerable<string> UnsupportedOutputFormats = new string[] { "mp4" };
|
||||
|
||||
public IEnumerable<string> VideoFormats
|
||||
{
|
||||
get
|
||||
|
@ -28,17 +22,23 @@ namespace MediaBrowser.Api.HttpHandlers
|
|||
/// </summary>
|
||||
protected override string GetOutputFormat()
|
||||
{
|
||||
return VideoFormats.First();
|
||||
return VideoFormats.First(f => !UnsupportedOutputFormats.Any(s => s.Equals(f, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
protected override bool RequiresConversion()
|
||||
{
|
||||
// If it's not in a format we can output to, return true
|
||||
if (UnsupportedOutputFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's not in a format the consumer accepts, return true
|
||||
if (!VideoFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
AudioStream audio = LibraryItem.AudioStreams.FirstOrDefault();
|
||||
|
||||
if (audio != null)
|
||||
|
@ -54,9 +54,16 @@ namespace MediaBrowser.Api.HttpHandlers
|
|||
return false;
|
||||
}
|
||||
|
||||
protected override Task WriteResponseToOutputStream(Stream stream)
|
||||
/// <summary>
|
||||
/// Creates arguments to pass to ffmpeg
|
||||
/// </summary>
|
||||
protected override string GetCommandLineArguments()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<string> audioTranscodeParams = new List<string>();
|
||||
|
||||
string outputFormat = GetOutputFormat();
|
||||
outputFormat = "matroska";
|
||||
return "-i \"" + LibraryItem.Path + "\" -vcodec copy -acodec copy -f " + outputFormat + " -";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,10 +86,10 @@
|
|||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ffmpeg\ffmpeg.exe" />
|
||||
<EmbeddedResource Include="FFMpeg\ffmpeg.exe" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="ffmpeg\readme.txt" />
|
||||
<Content Include="FFMpeg\readme.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -15,8 +14,8 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Users;
|
||||
using MediaBrowser.Model.Progress;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace MediaBrowser.Controller
|
||||
{
|
||||
|
|
|
@ -77,4 +77,7 @@ Global
|
|||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
Loading…
Reference in New Issue
Block a user