Forráskód Böngészése

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

LukePulverenti Luke Pulverenti luke pulverenti 13 éve
szülő
commit
bae04374e5

+ 53 - 39
MediaBrowser.Api/ApiService.cs

@@ -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;
+            }
+        }
     }
 }

+ 42 - 35
MediaBrowser.Api/HttpHandlers/AudioHandler.cs

@@ -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();
+            }
+        }
     }
 }

+ 19 - 12
MediaBrowser.Api/HttpHandlers/VideoHandler.cs

@@ -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 + " -";
         }
     }
 }

+ 2 - 2
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -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>

+ 1 - 2
MediaBrowser.Controller/Kernel.cs

@@ -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
 {

+ 3 - 0
MediaBrowser.sln

@@ -77,4 +77,7 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(Performance) = preSolution
+		HasPerformanceSessions = true
+	EndGlobalSection
 EndGlobal