ソースを参照

subtitle extraction fixes

Luke Pulverenti 12 年 前
コミット
33f4b2ed53

+ 17 - 12
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -77,8 +77,9 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         /// <param name="outputPath">The output path.</param>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
         /// <returns>System.String.</returns>
-        protected abstract string GetCommandLineArguments(string outputPath, StreamState state);
+        protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions);
 
         /// <summary>
         /// Gets the type of the transcoding job.
@@ -104,7 +105,7 @@ namespace MediaBrowser.Api.Playback
         protected string GetOutputFilePath(StreamState state)
         {
             var folder = ApplicationPaths.EncodedMediaCachePath;
-            return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state).GetMD5() + GetOutputFileExtension(state).ToLower());
+            return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + GetOutputFileExtension(state).ToLower());
         }
 
         /// <summary>
@@ -222,8 +223,9 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="outputVideoCodec">The output video codec.</param>
+        /// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
         /// <returns>System.String.</returns>
-        protected string GetOutputSizeParam(StreamState state, string outputVideoCodec)
+        protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion)
         {
             // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
 
@@ -235,7 +237,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1)
                 {
-                    assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks);
+                    assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks, performTextSubtitleConversion);
                 }
             }
 
@@ -287,10 +289,11 @@ namespace MediaBrowser.Api.Playback
         /// <param name="video">The video.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
+        /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
-        protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks)
+        protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
         {
-            var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks) : GetExtractedAssPath(video, subtitleStream, startTimeTicks);
+            var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks, performConversion) : GetExtractedAssPath(video, subtitleStream, startTimeTicks, performConversion);
 
             if (string.IsNullOrEmpty(path))
             {
@@ -306,14 +309,15 @@ namespace MediaBrowser.Api.Playback
         /// <param name="video">The video.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
+        /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
-        private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks)
+        private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
         {
             var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
 
             var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
 
-            if (!File.Exists(path))
+            if (performConversion && !File.Exists(path))
             {
                 InputType type;
 
@@ -340,8 +344,9 @@ namespace MediaBrowser.Api.Playback
         /// <param name="video">The video.</param>
         /// <param name="subtitleStream">The subtitle stream.</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
+        /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
         /// <returns>System.String.</returns>
-        private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks)
+        private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
         {
             var offset = startTimeTicks.HasValue
                           ? TimeSpan.FromTicks(startTimeTicks.Value)
@@ -349,7 +354,7 @@ namespace MediaBrowser.Api.Playback
 
             var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
 
-            if (!File.Exists(path))
+            if (performConversion && !File.Exists(path))
             {
                 try
                 {
@@ -381,7 +386,7 @@ namespace MediaBrowser.Api.Playback
             // Add resolution params, if specified
             if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
             {
-                outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
+                outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"');
                 outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
             }
 
@@ -552,7 +557,7 @@ namespace MediaBrowser.Api.Playback
 
                     FileName = MediaEncoder.EncoderPath,
                     WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
-                    Arguments = GetCommandLineArguments(outputPath, state),
+                    Arguments = GetCommandLineArguments(outputPath, state, true),
 
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false

+ 27 - 1
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -19,13 +19,24 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
+    /// <summary>
+    /// Class GetHlsAudioSegment
+    /// </summary>
     [Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")]
     [Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")]
     [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsAudioSegment
     {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
         public string Id { get; set; }
 
+        /// <summary>
+        /// Gets or sets the segment id.
+        /// </summary>
+        /// <value>The segment id.</value>
         public string SegmentId { get; set; }
     }
 
@@ -34,11 +45,24 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public class AudioHlsService : BaseHlsService
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AudioHlsService" /> class.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="isoManager">The iso manager.</param>
+        /// <param name="mediaEncoder">The media encoder.</param>
         public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
         public object Get(GetHlsAudioSegment request)
         {
             var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
@@ -93,8 +117,9 @@ namespace MediaBrowser.Api.Playback.Hls
         /// Gets the video arguments.
         /// </summary>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
         /// <returns>System.String.</returns>
-        protected override string GetVideoArguments(StreamState state)
+        protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
         {
             // No video
             return string.Empty;
@@ -105,6 +130,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// </summary>
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentException">Must specify either aac or mp3 audio codec.</exception>
         /// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
         protected override string GetSegmentFileExtension(StreamState state)
         {

+ 21 - 5
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -11,6 +11,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
+    /// <summary>
+    /// Class BaseHlsService
+    /// </summary>
     public abstract class BaseHlsService : BaseStreamingService
     {
         /// <summary>
@@ -18,6 +21,14 @@ namespace MediaBrowser.Api.Playback.Hls
         /// </summary>
         public const string SegmentFilePrefix = "segment-";
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="isoManager">The iso manager.</param>
+        /// <param name="mediaEncoder">The media encoder.</param>
         protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder) 
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
@@ -26,13 +37,16 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Gets the audio arguments.
         /// </summary>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
         protected abstract string GetAudioArguments(StreamState state);
         /// <summary>
         /// Gets the video arguments.
         /// </summary>
+        /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
         /// <returns>System.String.</returns>
-        protected abstract string GetVideoArguments(StreamState state);
+        protected abstract string GetVideoArguments(StreamState state, bool performSubtitleConversion);
 
         /// <summary>
         /// Gets the segment file extension.
@@ -40,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
         protected abstract string GetSegmentFileExtension(StreamState state);
-        
+
         /// <summary>
         /// Gets the type of the transcoding job.
         /// </summary>
@@ -53,6 +67,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Processes the request.
         /// </summary>
+        /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         protected object ProcessRequest(StreamRequest request)
         {
@@ -162,14 +177,15 @@ namespace MediaBrowser.Api.Playback.Hls
             }
             return count;
         }
-        
+
         /// <summary>
         /// Gets the command line arguments.
         /// </summary>
         /// <param name="outputPath">The output path.</param>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
         /// <returns>System.String.</returns>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
         {
             var segmentOutputPath = Path.GetDirectoryName(outputPath);
             var segmentOutputName = SegmentFilePrefix + Path.GetFileNameWithoutExtension(outputPath);
@@ -184,7 +200,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 GetInputArgument(state.Item, state.IsoMount),
                 GetSlowSeekCommandLineParameter(state.Request),
                 GetMapArgs(state),
-                GetVideoArguments(state),
+                GetVideoArguments(state, performSubtitleConversions),
                 GetAudioArguments(state),
                 outputPath,
                 segmentOutputPath

+ 35 - 4
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -8,6 +8,9 @@ using ServiceStack.ServiceHost;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
+    /// <summary>
+    /// Class GetHlsVideoStream
+    /// </summary>
     [Route("/Videos/{Id}/stream.m3u8", "GET")]
     [Api(Description = "Gets a video stream using HTTP live streaming.")]
     public class GetHlsVideoStream : VideoStreamRequest
@@ -15,22 +18,49 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
+    /// <summary>
+    /// Class GetHlsVideoSegment
+    /// </summary>
     [Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
     [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsVideoSegment
     {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
         public string Id { get; set; }
 
+        /// <summary>
+        /// Gets or sets the segment id.
+        /// </summary>
+        /// <value>The segment id.</value>
         public string SegmentId { get; set; }
     }
-    
+
+    /// <summary>
+    /// Class VideoHlsService
+    /// </summary>
     public class VideoHlsService : BaseHlsService
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="isoManager">The iso manager.</param>
+        /// <param name="mediaEncoder">The media encoder.</param>
         public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
         public object Get(GetHlsVideoSegment request)
         {
             var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
@@ -49,7 +79,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             return ProcessRequest(request);
         }
-        
+
         /// <summary>
         /// Gets the audio arguments.
         /// </summary>
@@ -105,8 +135,9 @@ namespace MediaBrowser.Api.Playback.Hls
         /// Gets the video arguments.
         /// </summary>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
         /// <returns>System.String.</returns>
-        protected override string GetVideoArguments(StreamState state)
+        protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
         {
             var codec = GetVideoCodec(state.VideoRequest);
 
@@ -128,7 +159,7 @@ namespace MediaBrowser.Api.Playback.Hls
             // Add resolution params, if specified
             if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
             {
-                args += GetOutputSizeParam(state, codec);
+                args += GetOutputSizeParam(state, codec, performSubtitleConversion);
             }
 
             // Get the output framerate based on the FrameRate param

+ 10 - 1
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -37,6 +37,14 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AudioService"/> class.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="isoManager">The iso manager.</param>
+        /// <param name="mediaEncoder">The media encoder.</param>
         public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
@@ -67,9 +75,10 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="outputPath">The output path.</param>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
         /// <returns>System.String.</returns>
         /// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
         {
             var request = state.Request;
 

+ 22 - 7
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,11 +1,11 @@
-using System.IO;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
-using System;
 using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
+using System;
+using System.IO;
 
 namespace MediaBrowser.Api.Playback.Progressive
 {
@@ -51,6 +51,14 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="VideoService"/> class.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="isoManager">The iso manager.</param>
+        /// <param name="mediaEncoder">The media encoder.</param>
         public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
@@ -66,6 +74,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, false);
         }
 
+        /// <summary>
+        /// Heads the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
         public object Head(GetVideoStream request)
         {
             return ProcessRequest(request, true);
@@ -76,8 +89,9 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="outputPath">The output path.</param>
         /// <param name="state">The state.</param>
+        /// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
         /// <returns>System.String.</returns>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state)
+        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
         {
             var video = (Video)state.Item;
 
@@ -103,7 +117,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 GetSlowSeekCommandLineParameter(state.Request),
                 keyFrame,
                 GetMapArgs(state),
-                GetVideoArguments(state, videoCodec),
+                GetVideoArguments(state, videoCodec, performSubtitleConversions),
                 threads,
                 GetAudioArguments(state),
                 format,
@@ -116,8 +130,9 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="codec">The video codec.</param>
+        /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
         /// <returns>System.String.</returns>
-        private string GetVideoArguments(StreamState state, string codec)
+        private string GetVideoArguments(StreamState state, string codec, bool performSubtitleConversion)
         {
             var args = "-vcodec " + codec;
 
@@ -136,7 +151,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             // Add resolution params, if specified
             if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
             {
-                args += GetOutputSizeParam(state, codec);
+                args += GetOutputSizeParam(state, codec, performSubtitleConversion);
             }
 
             if (request.Framerate.HasValue)

+ 112 - 8
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -558,6 +558,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
             {
                 StartInfo = new ProcessStartInfo
                 {
+                    RedirectStandardOutput = false,
+                    RedirectStandardError = true,
+                    
                     CreateNoWindow = true,
                     UseShellExecute = false,
                     FileName = FFMpegPath,
@@ -571,9 +574,57 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
             await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
-            var ranToCompletion = StartAndWaitForProcess(process);
+            var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
+
+            var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+
+            try
+            {
+                process.Start();
+            }
+            catch (Exception ex)
+            {
+                _subtitleExtractionResourcePool.Release();
+
+                logFileStream.Dispose();
+
+                _logger.ErrorException("Error starting ffmpeg", ex);
+
+                throw;
+            }
+
+            process.StandardError.BaseStream.CopyToAsync(logFileStream);
+
+            var ranToCompletion = process.WaitForExit(60000);
+
+            if (!ranToCompletion)
+            {
+                try
+                {
+                    _logger.Info("Killing ffmpeg process");
+
+                    process.Kill();
 
-            _subtitleExtractionResourcePool.Release();
+                    process.WaitForExit(1000);
+                }
+                catch (Win32Exception ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                catch (InvalidOperationException ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                catch (NotSupportedException ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                finally
+                {
+                    logFileStream.Dispose();
+                    _subtitleExtractionResourcePool.Release();
+                }
+            }
 
             var exitCode = ranToCompletion ? process.ExitCode : -1;
 
@@ -594,7 +645,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                     }
                     catch (IOException ex)
                     {
-                        _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath);
+                        _logger.ErrorException("Error converted extracted subtitle {0}", ex, outputPath);
                     }
                 }
             }
@@ -605,7 +656,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
             if (failed)
             {
-                var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath);
+                var msg = string.Format("ffmpeg subtitle converted failed for {0}", inputPath);
 
                 _logger.Error(msg);
 
@@ -669,6 +720,10 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 {
                     CreateNoWindow = true,
                     UseShellExecute = false,
+
+                    RedirectStandardOutput = false,
+                    RedirectStandardError = true,
+                    
                     FileName = FFMpegPath,
                     Arguments = string.Format("{0}-i {1} -map 0:{2} -an -vn -c:s ass \"{3}\"", offsetParam, inputPath, subtitleStreamIndex, outputPath),
                     WindowStyle = ProcessWindowStyle.Hidden,
@@ -680,9 +735,57 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
             await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
-            var ranToCompletion = StartAndWaitForProcess(process);
+            var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
+
+            var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+            
+            try
+            {
+                process.Start();
+            }
+            catch (Exception ex)
+            {
+                _subtitleExtractionResourcePool.Release();
+
+                logFileStream.Dispose();
+
+                _logger.ErrorException("Error starting ffmpeg", ex);
+
+                throw;
+            }
+
+            process.StandardError.BaseStream.CopyToAsync(logFileStream);
+
+            var ranToCompletion = process.WaitForExit(60000);
+
+            if (!ranToCompletion)
+            {
+                try
+                {
+                    _logger.Info("Killing ffmpeg process");
+
+                    process.Kill();
 
-            _subtitleExtractionResourcePool.Release();
+                    process.WaitForExit(1000);
+                }
+                catch (Win32Exception ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                catch (InvalidOperationException ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                catch (NotSupportedException ex)
+                {
+                    _logger.ErrorException("Error killing process", ex);
+                }
+                finally
+                {
+                    logFileStream.Dispose();
+                    _subtitleExtractionResourcePool.Release();
+                }
+            }
 
             var exitCode = ranToCompletion ? process.ExitCode : -1;
 
@@ -857,12 +960,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// Starts the and wait for process.
         /// </summary>
         /// <param name="process">The process.</param>
+        /// <param name="timeout">The timeout.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool StartAndWaitForProcess(Process process)
+        private bool StartAndWaitForProcess(Process process, int timeout = 10000)
         {
             process.Start();
 
-            var ranToCompletion = process.WaitForExit(10000);
+            var ranToCompletion = process.WaitForExit(timeout);
 
             if (!ranToCompletion)
             {