Fixed stdout/stderr deadlock issue that was causing ffmpeg to hang when working with large files.

pull/702/head
LukePulverenti Luke Pulverenti luke pulverenti 12 years ago
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…
Cancel
Save