mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-08 23:00:51 +02:00
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>
|
/// </summary>
|
||||||
public static class ApiService
|
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)
|
public static BaseItem GetItemById(string id)
|
||||||
{
|
{
|
||||||
Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id);
|
Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id);
|
||||||
|
@ -138,5 +99,58 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
return null;
|
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>
|
/// <summary>
|
||||||
/// Creates arguments to pass to ffmpeg
|
/// Creates arguments to pass to ffmpeg
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string GetAudioArguments()
|
protected override string GetCommandLineArguments()
|
||||||
{
|
{
|
||||||
List<string> audioTranscodeParams = new List<string>();
|
List<string> audioTranscodeParams = new List<string>();
|
||||||
|
|
||||||
|
@ -132,40 +132,6 @@ namespace MediaBrowser.Api.HttpHandlers
|
||||||
|
|
||||||
return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -";
|
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
|
public abstract class BaseMediaHandler<T> : BaseHandler
|
||||||
|
@ -252,7 +218,48 @@ namespace MediaBrowser.Api.HttpHandlers
|
||||||
base.ProcessRequest(ctx);
|
base.ProcessRequest(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract string GetCommandLineArguments();
|
||||||
protected abstract string GetOutputFormat();
|
protected abstract string GetOutputFormat();
|
||||||
protected abstract bool RequiresConversion();
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
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;
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.HttpHandlers
|
namespace MediaBrowser.Api.HttpHandlers
|
||||||
{
|
{
|
||||||
class VideoHandler : BaseMediaHandler<Video>
|
class VideoHandler : BaseMediaHandler<Video>
|
||||||
{
|
{
|
||||||
|
private IEnumerable<string> UnsupportedOutputFormats = new string[] { "mp4" };
|
||||||
|
|
||||||
public IEnumerable<string> VideoFormats
|
public IEnumerable<string> VideoFormats
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -28,11 +22,17 @@ namespace MediaBrowser.Api.HttpHandlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override string GetOutputFormat()
|
protected override string GetOutputFormat()
|
||||||
{
|
{
|
||||||
return VideoFormats.First();
|
return VideoFormats.First(f => !UnsupportedOutputFormats.Any(s => s.Equals(f, StringComparison.OrdinalIgnoreCase)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool RequiresConversion()
|
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 it's not in a format the consumer accepts, return true
|
||||||
if (!VideoFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
|
if (!VideoFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
|
@ -54,9 +54,16 @@ namespace MediaBrowser.Api.HttpHandlers
|
||||||
return false;
|
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" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="ffmpeg\ffmpeg.exe" />
|
<EmbeddedResource Include="FFMpeg\ffmpeg.exe" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="ffmpeg\readme.txt" />
|
<Content Include="FFMpeg\readme.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -5,7 +5,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Kernel;
|
using MediaBrowser.Common.Kernel;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -15,8 +14,8 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Users;
|
|
||||||
using MediaBrowser.Model.Progress;
|
using MediaBrowser.Model.Progress;
|
||||||
|
using MediaBrowser.Model.Users;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller
|
namespace MediaBrowser.Controller
|
||||||
{
|
{
|
||||||
|
|
|
@ -77,4 +77,7 @@ Global
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(Performance) = preSolution
|
||||||
|
HasPerformanceSessions = true
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in a new issue