Browse Source

extracted a media encoding interface to keep ffmpeg out of nuget packages

Luke Pulverenti 12 years ago
parent
commit
cb39f8e7b5
37 changed files with 1430 additions and 1179 deletions
  1. 52 11
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  2. 3 2
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  3. 6 6
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  4. 3 2
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  5. 3 2
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  6. 4 3
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  7. 4 3
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  8. 33 0
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  9. 1 20
      MediaBrowser.Common/IO/FileSystemRepository.cs
  10. 6 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  11. 102 0
      MediaBrowser.Common/MediaInfo/IMediaEncoder.cs
  12. 11 9
      MediaBrowser.Common/MediaInfo/MediaInfoResult.cs
  13. 1 0
      MediaBrowser.Common/packages.config
  14. 1 24
      MediaBrowser.Controller/Drawing/ImageManager.cs
  15. 2 10
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  16. 19 958
      MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
  17. 3 4
      MediaBrowser.Controller/Providers/IProviderManager.cs
  18. 6 2
      MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs
  19. 69 30
      MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs
  20. 9 9
      MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs
  21. 22 23
      MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs
  22. 6 5
      MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs
  23. 6 26
      MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs
  24. 96 0
      MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs
  25. 13 2
      MediaBrowser.Model/Querying/ItemQuery.cs
  26. 0 1
      MediaBrowser.Model/packages.config
  27. 5 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  28. 930 0
      MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
  29. 0 0
      MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id
  30. 0 0
      MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
  31. 0 0
      MediaBrowser.Server.Implementations/MediaEncoder/readme.txt
  32. 0 20
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  33. 1 1
      MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
  34. 8 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  35. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  36. 1 1
      Nuget/MediaBrowser.Common.nuspec
  37. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 52 - 11
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,8 +1,10 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers.MediaInfo;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -46,6 +48,12 @@ namespace MediaBrowser.Api.Playback
         /// <value>The iso manager.</value>
         protected IIsoManager IsoManager { get; set; }
 
+        /// <summary>
+        /// Gets or sets the media encoder.
+        /// </summary>
+        /// <value>The media encoder.</value>
+        protected IMediaEncoder MediaEncoder { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
@@ -53,12 +61,14 @@ namespace MediaBrowser.Api.Playback
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
-        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
+        /// <param name="mediaEncoder">The media encoder.</param>
+        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
         {
             ApplicationPaths = appPaths;
             UserManager = userManager;
             LibraryManager = libraryManager;
             IsoManager = isoManager;
+            MediaEncoder = mediaEncoder;
         }
 
         /// <summary>
@@ -309,9 +319,17 @@ namespace MediaBrowser.Api.Playback
 
             if (!File.Exists(path))
             {
-                var success = Kernel.Instance.FFMpegManager.ExtractTextSubtitle(video, subtitleStream.Index, path, CancellationToken.None).Result;
+                InputType type;
+
+                var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
 
-                if (!success)
+                try
+                {
+                    var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, subtitleStream.Index, path, CancellationToken.None);
+
+                    Task.WaitAll(task);
+                }
+                catch
                 {
                     return null;
                 }
@@ -332,9 +350,13 @@ namespace MediaBrowser.Api.Playback
 
             if (!File.Exists(path))
             {
-                var success = Kernel.Instance.FFMpegManager.ConvertTextSubtitle(subtitleStream, path, CancellationToken.None).Result;
+                try
+                {
+                    var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, CancellationToken.None);
 
-                if (!success)
+                    Task.WaitAll(task);
+                }
+                catch
                 {
                     return null;
                 }
@@ -365,6 +387,16 @@ namespace MediaBrowser.Api.Playback
             return string.Format(" -filter_complex \"[0:{0}]format=yuva444p,lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"", state.SubtitleStream.Index, state.VideoStream.Index, outputSizeParam);
         }
 
+        /// <summary>
+        /// Gets the probe size argument.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        protected string GetProbeSizeArgument(BaseItem item)
+        {
+            return MediaEncoder.GetProbeSizeArgument(MediaEncoderHelpers.GetInputType(item));
+        }
+
         /// <summary>
         /// Gets the number of audio channels to specify on the command line
         /// </summary>
@@ -477,9 +509,18 @@ namespace MediaBrowser.Api.Playback
         /// <returns>System.String.</returns>
         protected string GetInputArgument(BaseItem item, IIsoMount isoMount)
         {
-            return isoMount == null ?
-                Kernel.Instance.FFMpegManager.GetInputArgument(item) :
-                Kernel.Instance.FFMpegManager.GetInputArgument(item as Video, isoMount);
+            var type = InputType.AudioFile;
+
+            var inputPath = new[] { item.Path };
+
+            var video = item as Video;
+
+            if (video != null)
+            {
+                inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+            }
+
+            return MediaEncoder.GetInputArgument(inputPath, type);
         }
 
         /// <summary>
@@ -508,8 +549,8 @@ namespace MediaBrowser.Api.Playback
                     RedirectStandardOutput = true,
                     RedirectStandardError = true,
 
-                    FileName = Kernel.Instance.FFMpegManager.FFMpegPath,
-                    WorkingDirectory = Path.GetDirectoryName(Kernel.Instance.FFMpegManager.FFMpegPath),
+                    FileName = MediaEncoder.EncoderPath,
+                    WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
                     Arguments = GetCommandLineArguments(outputPath, state),
 
                     WindowStyle = ProcessWindowStyle.Hidden,
@@ -655,7 +696,7 @@ namespace MediaBrowser.Api.Playback
             }
 
             state.AudioStream = GetMediaStream(media.MediaStreams, null, MediaStreamType.Audio, true);
-            
+
             return state;
         }
 

+ 3 - 2
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
@@ -33,8 +34,8 @@ namespace MediaBrowser.Api.Playback.Hls
     /// </summary>
     public class AudioHlsService : BaseHlsService
     {
-        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
-            : base(appPaths, userManager, libraryManager, isoManager)
+        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 

+ 6 - 6
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -1,12 +1,12 @@
-using System.Collections.Generic;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using System.Text;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Playback.Hls
@@ -18,8 +18,8 @@ namespace MediaBrowser.Api.Playback.Hls
         /// </summary>
         public const string SegmentFilePrefix = "segment-";
 
-        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager) 
-            : base(appPaths, userManager, libraryManager, isoManager)
+        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder) 
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 
@@ -176,7 +176,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             segmentOutputPath = Path.Combine(segmentOutputPath, segmentOutputName + "%03d." + GetSegmentFileExtension(state).TrimStart('.'));
 
-            var probeSize = Kernel.Instance.FFMpegManager.GetProbeSizeArgument(state.Item);
+            var probeSize = GetProbeSizeArgument(state.Item);
 
             return string.Format("{0} {1} -i {2}{3} -threads 0 {4} {5} {6} -f ssegment -segment_list_flags +live -segment_time 10 -segment_list \"{7}\" \"{8}\"",
                 probeSize,

+ 3 - 2
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -1,5 +1,6 @@
 using System.IO;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using System;
@@ -25,8 +26,8 @@ namespace MediaBrowser.Api.Playback.Hls
     
     public class VideoHlsService : BaseHlsService
     {
-        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
-            : base(appPaths, userManager, libraryManager, isoManager)
+        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using ServiceStack.ServiceHost;
@@ -36,8 +37,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     {
-        public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
-            : base(appPaths, userManager, libraryManager, isoManager)
+        public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,16 +1,17 @@
 using MediaBrowser.Api.Images;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.Playback.Progressive
 {
@@ -19,8 +20,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public abstract class BaseProgressiveStreamingService : BaseStreamingService
     {
-        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager) :
-            base(appPaths, userManager, libraryManager, isoManager)
+        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder) :
+            base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,5 +1,6 @@
 using System.IO;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using System;
@@ -46,8 +47,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     {
-        public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
-            : base(appPaths, userManager, libraryManager, isoManager)
+        public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
         {
         }
 
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
             var video = (Video)state.Item;
 
-            var probeSize = Kernel.Instance.FFMpegManager.GetProbeSizeArgument(video.VideoType, video.IsoType);
+            var probeSize = GetProbeSizeArgument(state.Item);
 
             // Get the output codec name
             var videoCodec = GetVideoCodec(state.VideoRequest);

+ 33 - 0
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
@@ -127,6 +128,20 @@ namespace MediaBrowser.Api.UserLibrary
         /// <value>The video formats.</value>
         [ApiMember(Name = "VideoFormats", Description = "Optional filter by VideoFormat (Standard, Digital3D, Sbs3D). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string VideoFormats { get; set; }
+
+        /// <summary>
+        /// Gets or sets the series status.
+        /// </summary>
+        /// <value>The series status.</value>
+        [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string SeriesStatus { get; set; }
+
+        /// <summary>
+        /// Gets or sets the air days.
+        /// </summary>
+        /// <value>The air days.</value>
+        [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string AirDays { get; set; }
     }
 
     /// <summary>
@@ -340,6 +355,23 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{BaseItem}.</returns>
         private IEnumerable<BaseItem> ApplyAdditionalFilters(GetItems request, IEnumerable<BaseItem> items)
         {
+            // Filter by Series Status
+            if (!string.IsNullOrEmpty(request.SeriesStatus))
+            {
+                var vals = request.SeriesStatus.Split(',');
+
+                items = items.OfType<Series>().Where(i => i.Status.HasValue && vals.Contains(i.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
+            }
+
+            // Filter by Series AirDays
+            if (!string.IsNullOrEmpty(request.AirDays))
+            {
+                var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
+
+                items = items.OfType<Series>().Where(i => i.AirDays != null && days.Any(d => i.AirDays.Contains(d)));
+            }
+
+            // Filter by VideoFormat
             if (!string.IsNullOrEmpty(request.VideoFormats))
             {
                 var formats = request.VideoFormats.Split(',');
@@ -347,6 +379,7 @@ namespace MediaBrowser.Api.UserLibrary
                 items = items.OfType<Video>().Where(i => formats.Contains(i.VideoFormat.ToString(), StringComparer.OrdinalIgnoreCase));
             }
 
+            // Filter by VideoType
             if (!string.IsNullOrEmpty(request.VideoTypes))
             {
                 var types = request.VideoTypes.Split(',');

+ 1 - 20
MediaBrowser.Common/IO/FileSystemRepository.cs

@@ -9,7 +9,7 @@ namespace MediaBrowser.Common.IO
     /// This is a wrapper for storing large numbers of files within a directory on a file system.
     /// Simply pass a filename into GetResourcePath and it will return a full path location of where the file should be stored.
     /// </summary>
-    public class FileSystemRepository : IDisposable
+    public class FileSystemRepository
     {
         /// <summary>
         /// Contains the list of subfolders under the main directory
@@ -163,24 +163,5 @@ namespace MediaBrowser.Common.IO
             
             return File.Exists(path);
         }
-
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-            }
-        }
     }
 }

+ 6 - 0
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -38,6 +38,10 @@
     </ApplicationIcon>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="protobuf-net, Version=2.0.0.621, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll</HintPath>
+    </Reference>
     <Reference Include="ServiceStack.Common, Version=3.9.43.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\ServiceStack.Common.3.9.43\lib\net35\ServiceStack.Common.dll</HintPath>
@@ -70,6 +74,8 @@
     <Compile Include="IO\IIsoMount.cs" />
     <Compile Include="IO\ProgressStream.cs" />
     <Compile Include="IO\StreamDefaults.cs" />
+    <Compile Include="MediaInfo\MediaInfoResult.cs" />
+    <Compile Include="MediaInfo\IMediaEncoder.cs" />
     <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />

+ 102 - 0
MediaBrowser.Common/MediaInfo/IMediaEncoder.cs

@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.MediaInfo
+{
+    /// <summary>
+    /// Interface IMediaEncoder
+    /// </summary>
+    public interface IMediaEncoder
+    {
+        /// <summary>
+        /// Gets the encoder path.
+        /// </summary>
+        /// <value>The encoder path.</value>
+        string EncoderPath { get; }
+
+        /// <summary>
+        /// Gets the version.
+        /// </summary>
+        /// <value>The version.</value>
+        string Version { get; }
+
+        /// <summary>
+        /// Extracts the image.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="offset">The offset.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task ExtractImage(string[] inputFiles, InputType type, TimeSpan? offset, string outputPath, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Extracts the text subtitle.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Converts the text subtitle to ass.
+        /// </summary>
+        /// <param name="inputPath">The input path.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task ConvertTextSubtitleToAss(string inputPath, string outputPath, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the media info.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task<MediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the probe size argument.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>System.String.</returns>
+        string GetProbeSizeArgument(InputType type);
+
+        /// <summary>
+        /// Gets the input argument.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <returns>System.String.</returns>
+        string GetInputArgument(string[] inputFiles, InputType type);
+    }
+
+    /// <summary>
+    /// Enum InputType
+    /// </summary>
+    public enum InputType
+    {
+        /// <summary>
+        /// The audio file
+        /// </summary>
+        AudioFile,
+        /// <summary>
+        /// The video file
+        /// </summary>
+        VideoFile,
+        /// <summary>
+        /// The bluray
+        /// </summary>
+        Bluray,
+        /// <summary>
+        /// The DVD
+        /// </summary>
+        Dvd
+    }
+}

+ 11 - 9
MediaBrowser.Controller/MediaInfo/FFProbeResult.cs → MediaBrowser.Common/MediaInfo/MediaInfoResult.cs

@@ -2,30 +2,32 @@
 using ProtoBuf;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Controller.MediaInfo
+namespace MediaBrowser.Common.MediaInfo
 {
     /// <summary>
-    /// Provides a class that we can use to deserialize the ffprobe json output
-    /// Sample output:
-    /// http://stackoverflow.com/questions/7708373/get-ffmpeg-information-in-friendly-way
+    /// Class MediaInfoResult
     /// </summary>
     [ProtoContract]
-    public class FFProbeResult
+    public class MediaInfoResult
     {
         /// <summary>
         /// Gets or sets the streams.
         /// </summary>
         /// <value>The streams.</value>
         [ProtoMember(1)]
-        public FFProbeMediaStreamInfo[] streams { get; set; }
+        public MediaStreamInfo[] streams { get; set; }
 
         /// <summary>
         /// Gets or sets the format.
         /// </summary>
         /// <value>The format.</value>
         [ProtoMember(2)]
-        public FFProbeMediaFormatInfo format { get; set; }
+        public MediaFormatInfo format { get; set; }
 
+        /// <summary>
+        /// Gets or sets the chapters.
+        /// </summary>
+        /// <value>The chapters.</value>
         [ProtoMember(3)]
         public List<ChapterInfo> Chapters { get; set; }
     }
@@ -34,7 +36,7 @@ namespace MediaBrowser.Controller.MediaInfo
     /// Represents a stream within the output
     /// </summary>
     [ProtoContract]
-    public class FFProbeMediaStreamInfo
+    public class MediaStreamInfo
     {
         /// <summary>
         /// Gets or sets the index.
@@ -286,7 +288,7 @@ namespace MediaBrowser.Controller.MediaInfo
     /// Class MediaFormat
     /// </summary>
     [ProtoContract]
-    public class FFProbeMediaFormatInfo
+    public class MediaFormatInfo
     {
         /// <summary>
         /// Gets or sets the filename.

+ 1 - 0
MediaBrowser.Common/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="protobuf-net" version="2.0.0.621" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.43" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.43" targetFramework="net45" />
 </packages>

+ 1 - 24
MediaBrowser.Controller/Drawing/ImageManager.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Drawing
     /// <summary>
     /// Class ImageManager
     /// </summary>
-    public class ImageManager : IDisposable
+    public class ImageManager
     {
         /// <summary>
         /// Gets the image size cache.
@@ -681,28 +681,5 @@ namespace MediaBrowser.Controller.Drawing
         {
             return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
         }
-
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                ImageSizeCache.Dispose();
-                ResizedImageCache.Dispose();
-                CroppedImageCache.Dispose();
-                EnhancedImageCache.Dispose();
-            }
-        }
     }
 }

+ 2 - 10
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -53,8 +53,7 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="protobuf-net, Version=2.0.0.621, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="protobuf-net">
       <HintPath>..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll</HintPath>
     </Reference>
     <Reference Include="System" />
@@ -111,6 +110,7 @@
     <Compile Include="Library\ChildrenChangedEventArgs.cs" />
     <Compile Include="Library\DtoBuilder.cs" />
     <Compile Include="Providers\IProviderManager.cs" />
+    <Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" />
     <Compile Include="Providers\MetadataProviderPriority.cs" />
     <Compile Include="Providers\Music\LastfmAlbumProvider.cs" />
     <Compile Include="Providers\Music\FanArtAlbumProvider.cs" />
@@ -134,7 +134,6 @@
     <Compile Include="Localization\RatingsDefinition.cs" />
     <Compile Include="Localization\USRatingsDictionary.cs" />
     <Compile Include="MediaInfo\FFMpegManager.cs" />
-    <Compile Include="MediaInfo\FFProbeResult.cs" />
     <Compile Include="Persistence\IDisplayPreferencesRepository.cs" />
     <Compile Include="Persistence\IItemRepository.cs" />
     <Compile Include="Persistence\IRepository.cs" />
@@ -201,15 +200,8 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="MediaInfo\fonts\ARIALUNI.TTF" />
-    <EmbeddedResource Include="MediaInfo\fonts\fonts.conf" />
-    <EmbeddedResource Include="MediaInfo\ffmpeg20130405.zip" />
     <None Include="packages.config" />
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="MediaInfo\readme.txt" />
-  </ItemGroup>
-  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>if $(ConfigurationName) == Release (

+ 19 - 958
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -1,19 +1,11 @@
-using System.Text;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers.MediaInfo;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
 using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -22,7 +14,7 @@ namespace MediaBrowser.Controller.MediaInfo
     /// <summary>
     /// Class FFMpegManager
     /// </summary>
-    public class FFMpegManager : IDisposable
+    public class FFMpegManager
     {
         /// <summary>
         /// Gets or sets the video image cache.
@@ -42,170 +34,35 @@ namespace MediaBrowser.Controller.MediaInfo
         /// <value>The subtitle cache.</value>
         internal FileSystemRepository SubtitleCache { get; set; }
 
-        /// <summary>
-        /// Gets or sets the zip client.
-        /// </summary>
-        /// <value>The zip client.</value>
-        private readonly IZipClient _zipClient;
-
         /// <summary>
         /// The _logger
         /// </summary>
         private readonly Kernel _kernel;
 
-        /// <summary>
-        /// The _logger
-        /// </summary>
-        private readonly ILogger _logger;
-
-        /// <summary>
-        /// Gets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
-        private readonly IJsonSerializer _jsonSerializer;
-
-        /// <summary>
-        /// The _protobuf serializer
-        /// </summary>
-        private readonly IProtobufSerializer _protobufSerializer;
         private readonly IServerApplicationPaths _appPaths;
+        private readonly IMediaEncoder _encoder;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="FFMpegManager" /> class.
         /// </summary>
         /// <param name="kernel">The kernel.</param>
-        /// <param name="zipClient">The zip client.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="protobufSerializer">The protobuf serializer.</param>
-        /// <param name="logger">The logger.</param>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="encoder">The encoder.</param>
         /// <exception cref="System.ArgumentNullException">zipClient</exception>
-        public FFMpegManager(Kernel kernel, IZipClient zipClient, IJsonSerializer jsonSerializer, IProtobufSerializer protobufSerializer, ILogManager logManager, IServerApplicationPaths appPaths)
+        public FFMpegManager(Kernel kernel, IServerApplicationPaths appPaths, IMediaEncoder encoder)
         {
             if (kernel == null)
             {
                 throw new ArgumentNullException("kernel");
             }
-            if (zipClient == null)
-            {
-                throw new ArgumentNullException("zipClient");
-            }
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
-            if (protobufSerializer == null)
-            {
-                throw new ArgumentNullException("protobufSerializer");
-            }
 
             _kernel = kernel;
-            _zipClient = zipClient;
-            _jsonSerializer = jsonSerializer;
-            _protobufSerializer = protobufSerializer;
             _appPaths = appPaths;
-            _logger = logManager.GetLogger("FFMpegManager");
-
-            // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes
-            SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX);
+            _encoder = encoder;
 
             VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
             AudioImageCache = new FileSystemRepository(AudioImagesDataPath);
             SubtitleCache = new FileSystemRepository(SubtitleCachePath);
-
-            Task.Run(() => VersionedDirectoryPath = GetVersionedDirectoryPath());
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                SetErrorMode(ErrorModes.SYSTEM_DEFAULT);
-
-                AudioImageCache.Dispose();
-                VideoImageCache.Dispose();
-            }
-        }
-
-        /// <summary>
-        /// The FF probe resource pool count
-        /// </summary>
-        private const int FFProbeResourcePoolCount = 3;
-        /// <summary>
-        /// The audio image resource pool count
-        /// </summary>
-        private const int AudioImageResourcePoolCount = 3;
-        /// <summary>
-        /// The video image resource pool count
-        /// </summary>
-        private const int VideoImageResourcePoolCount = 2;
-
-        /// <summary>
-        /// The FF probe resource pool
-        /// </summary>
-        private readonly SemaphoreSlim FFProbeResourcePool = new SemaphoreSlim(FFProbeResourcePoolCount, FFProbeResourcePoolCount);
-        /// <summary>
-        /// The audio image resource pool
-        /// </summary>
-        private readonly SemaphoreSlim AudioImageResourcePool = new SemaphoreSlim(AudioImageResourcePoolCount, AudioImageResourcePoolCount);
-        /// <summary>
-        /// The video image resource pool
-        /// </summary>
-        private readonly SemaphoreSlim VideoImageResourcePool = new SemaphoreSlim(VideoImageResourcePoolCount, VideoImageResourcePoolCount);
-
-        /// <summary>
-        /// Gets or sets the versioned directory path.
-        /// </summary>
-        /// <value>The versioned directory path.</value>
-        private string VersionedDirectoryPath { get; set; }
-
-        /// <summary>
-        /// Gets the FFMPEG version.
-        /// </summary>
-        /// <value>The FFMPEG version.</value>
-        public string FFMpegVersion
-        {
-            get { return Path.GetFileNameWithoutExtension(VersionedDirectoryPath); }
-        }
-
-        /// <summary>
-        /// The _ FF MPEG path
-        /// </summary>
-        private string _FFMpegPath;
-        /// <summary>
-        /// Gets the path to ffmpeg.exe
-        /// </summary>
-        /// <value>The FF MPEG path.</value>
-        public string FFMpegPath
-        {
-            get
-            {
-                return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe"));
-            }
-        }
-
-        /// <summary>
-        /// The _ FF probe path
-        /// </summary>
-        private string _FFProbePath;
-        /// <summary>
-        /// Gets the path to ffprobe.exe
-        /// </summary>
-        /// <value>The FF probe path.</value>
-        public string FFProbePath
-        {
-            get
-            {
-                return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe"));
-            }
         }
 
         /// <summary>
@@ -222,7 +79,7 @@ namespace MediaBrowser.Controller.MediaInfo
             {
                 if (_videoImagesDataPath == null)
                 {
-                    _videoImagesDataPath = Path.Combine(_appPaths.DataPath, "ffmpeg-video-images");
+                    _videoImagesDataPath = Path.Combine(_appPaths.DataPath, "extracted-video-images");
 
                     if (!Directory.Exists(_videoImagesDataPath))
                     {
@@ -248,7 +105,7 @@ namespace MediaBrowser.Controller.MediaInfo
             {
                 if (_audioImagesDataPath == null)
                 {
-                    _audioImagesDataPath = Path.Combine(_appPaths.DataPath, "ffmpeg-audio-images");
+                    _audioImagesDataPath = Path.Combine(_appPaths.DataPath, "extracted-audio-images");
 
                     if (!Directory.Exists(_audioImagesDataPath))
                     {
@@ -274,7 +131,7 @@ namespace MediaBrowser.Controller.MediaInfo
             {
                 if (_subtitleCachePath == null)
                 {
-                    _subtitleCachePath = Path.Combine(_appPaths.CachePath, "ffmpeg-subtitles");
+                    _subtitleCachePath = Path.Combine(_appPaths.CachePath, "subtitles");
 
                     if (!Directory.Exists(_subtitleCachePath))
                     {
@@ -286,386 +143,10 @@ namespace MediaBrowser.Controller.MediaInfo
             }
         }
 
-        /// <summary>
-        /// The _media tools path
-        /// </summary>
-        private string _mediaToolsPath;
-        /// <summary>
-        /// Gets the folder path to tools
-        /// </summary>
-        /// <value>The media tools path.</value>
-        private string MediaToolsPath
-        {
-            get
-            {
-                if (_mediaToolsPath == null)
-                {
-                    _mediaToolsPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
-
-                    if (!Directory.Exists(_mediaToolsPath))
-                    {
-                        Directory.CreateDirectory(_mediaToolsPath);
-                    }
-                }
-
-                return _mediaToolsPath;
-            }
-        }
-
-        /// <summary>
-        /// Gets the versioned directory path.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        private string GetVersionedDirectoryPath()
-        {
-            var assembly = GetType().Assembly;
-
-            const string prefix = "MediaBrowser.Controller.MediaInfo.";
-            const string srch = prefix + "ffmpeg";
-
-            var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch));
-
-            var filename = resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length);
-
-            var versionedDirectoryPath = Path.Combine(MediaToolsPath, Path.GetFileNameWithoutExtension(filename));
-
-            if (!Directory.Exists(versionedDirectoryPath))
-            {
-                Directory.CreateDirectory(versionedDirectoryPath);
-            }
-
-            ExtractTools(assembly, resource, versionedDirectoryPath);
-
-            return versionedDirectoryPath;
-        }
-
-        /// <summary>
-        /// Extracts the tools.
-        /// </summary>
-        /// <param name="assembly">The assembly.</param>
-        /// <param name="zipFileResourcePath">The zip file resource path.</param>
-        /// <param name="targetPath">The target path.</param>
-        private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
-        {
-            using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
-            {
-                _zipClient.ExtractAll(resourceStream, targetPath, false);
-            }
-
-            ExtractFonts(assembly, targetPath);
-        }
-
-        /// <summary>
-        /// Extracts the fonts.
-        /// </summary>
-        /// <param name="assembly">The assembly.</param>
-        /// <param name="targetPath">The target path.</param>
-        private async void ExtractFonts(Assembly assembly, string targetPath)
-        {
-            var fontsDirectory = Path.Combine(targetPath, "fonts");
-
-            if (!Directory.Exists(fontsDirectory))
-            {
-                Directory.CreateDirectory(fontsDirectory);
-            }
-
-            const string fontFilename = "ARIALUNI.TTF";
-
-            var fontFile = Path.Combine(fontsDirectory, fontFilename);
-
-            if (!File.Exists(fontFile))
-            {
-                using (var stream = assembly.GetManifestResourceStream("MediaBrowser.Controller.MediaInfo.fonts." + fontFilename))
-                {
-                    using (var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
-                    {
-                        await stream.CopyToAsync(fileStream).ConfigureAwait(false);
-                    }
-                }
-            }
-
-            await ExtractFontConfigFile(assembly, fontsDirectory).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Extracts the font config file.
-        /// </summary>
-        /// <param name="assembly">The assembly.</param>
-        /// <param name="fontsDirectory">The fonts directory.</param>
-        private async Task ExtractFontConfigFile(Assembly assembly, string fontsDirectory)
-        {
-            const string fontConfigFilename = "fonts.conf";
-            var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
-
-            if (!File.Exists(fontConfigFile))
-            {
-                using (var stream = assembly.GetManifestResourceStream("MediaBrowser.Controller.MediaInfo.fonts." + fontConfigFilename))
-                {
-                    using (var streamReader = new StreamReader(stream))
-                    {
-                        var contents = await streamReader.ReadToEndAsync().ConfigureAwait(false);
-
-                        contents = contents.Replace("<dir></dir>", "<dir>" + fontsDirectory + "</dir>");
-
-                        var bytes = Encoding.UTF8.GetBytes(contents);
-
-                        using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
-                        {
-                            await fileStream.WriteAsync(bytes, 0, bytes.Length);
-                        }
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the probe size argument.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetProbeSizeArgument(BaseItem item)
-        {
-            var video = item as Video;
-
-            return video != null ? GetProbeSizeArgument(video.VideoType, video.IsoType) : string.Empty;
-        }
-
-        /// <summary>
-        /// Gets the probe size argument.
-        /// </summary>
-        /// <param name="videoType">Type of the video.</param>
-        /// <param name="isoType">Type of the iso.</param>
-        /// <returns>System.String.</returns>
-        public string GetProbeSizeArgument(VideoType videoType, IsoType? isoType)
-        {
-            if (videoType == VideoType.Dvd || (isoType.HasValue && isoType.Value == IsoType.Dvd))
-            {
-                return "-probesize 1G -analyzeduration 200M";
-            }
-
-            return string.Empty;
-        }
-
-        /// <summary>
-        /// Runs FFProbe against a BaseItem
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="inputPath">The input path.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cache">The cache.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{FFProbeResult}.</returns>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        public Task<FFProbeResult> RunFFProbe(BaseItem item, string inputPath, DateTime lastDateModified, FileSystemRepository cache, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(inputPath))
-            {
-                throw new ArgumentNullException("inputPath");
-            }
-
-            if (cache == null)
-            {
-                throw new ArgumentNullException("cache");
-            }
-
-            // Put the ffmpeg version into the cache name so that it's unique per-version
-            // We don't want to try and deserialize data based on an old version, which could potentially fail
-            var resourceName = item.Id + "_" + lastDateModified.Ticks + "_" + FFMpegVersion;
-
-            // Forumulate the cache file path
-            var cacheFilePath = cache.GetResourcePath(resourceName, ".pb");
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            // Avoid File.Exists by just trying to deserialize
-            try
-            {
-                return Task.FromResult(_protobufSerializer.DeserializeFromFile<FFProbeResult>(cacheFilePath));
-            }
-            catch (FileNotFoundException)
-            {
-                var extractChapters = false;
-                var video = item as Video;
-                var probeSizeArgument = string.Empty;
-
-                if (video != null)
-                {
-                    extractChapters = true;
-                    probeSizeArgument = GetProbeSizeArgument(video.VideoType, video.IsoType);
-                }
-
-                return RunFFProbeInternal(inputPath, extractChapters, cacheFilePath, probeSizeArgument, cancellationToken);
-            }
-        }
-
-        /// <summary>
-        /// Runs FFProbe against a BaseItem
-        /// </summary>
-        /// <param name="inputPath">The input path.</param>
-        /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
-        /// <param name="cacheFile">The cache file.</param>
-        /// <param name="probeSizeArgument">The probe size argument.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{FFProbeResult}.</returns>
-        /// <exception cref="System.ApplicationException"></exception>
-        private async Task<FFProbeResult> RunFFProbeInternal(string inputPath, bool extractChapters, string cacheFile, string probeSizeArgument, CancellationToken cancellationToken)
-        {
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-
-                    // Must consume both or ffmpeg may hang due to deadlocks. See comments below.   
-                    RedirectStandardOutput = true,
-                    RedirectStandardError = true,
-                    FileName = FFProbePath,
-                    Arguments = string.Format("{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format", probeSizeArgument, inputPath).Trim(),
-
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                },
-
-                EnableRaisingEvents = true
-            };
-
-            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
-            process.Exited += ProcessExited;
-
-            await FFProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            FFProbeResult result;
-            string standardError = null;
-
-            try
-            {
-                process.Start();
-
-                Task<string> standardErrorReadTask = null;
-
-                // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-                if (extractChapters)
-                {
-                    standardErrorReadTask = process.StandardError.ReadToEndAsync();
-                }
-                else
-                {
-                    process.BeginErrorReadLine();
-                }
-
-                result = _jsonSerializer.DeserializeFromStream<FFProbeResult>(process.StandardOutput.BaseStream);
-
-                if (extractChapters)
-                {
-                    standardError = await standardErrorReadTask.ConfigureAwait(false);
-                }
-            }
-            catch
-            {
-                // Hate having to do this
-                try
-                {
-                    process.Kill();
-                }
-                catch (InvalidOperationException ex1)
-                {
-                    _logger.ErrorException("Error killing ffprobe", ex1);
-                }
-                catch (Win32Exception ex1)
-                {
-                    _logger.ErrorException("Error killing ffprobe", ex1);
-                }
-
-                throw;
-            }
-            finally
-            {
-                FFProbeResourcePool.Release();
-            }
-
-            if (result == null)
-            {
-                throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath));
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (extractChapters && !string.IsNullOrEmpty(standardError))
-            {
-                AddChapters(result, standardError);
-            }
-
-            _protobufSerializer.SerializeToFile(result, cacheFile);
-
-            return result;
-        }
-
-        /// <summary>
-        /// Adds the chapters.
-        /// </summary>
-        /// <param name="result">The result.</param>
-        /// <param name="standardError">The standard error.</param>
-        private void AddChapters(FFProbeResult result, string standardError)
-        {
-            var lines = standardError.Split('\n').Select(l => l.TrimStart());
-
-            var chapters = new List<ChapterInfo> { };
-
-            ChapterInfo lastChapter = null;
-
-            foreach (var line in lines)
-            {
-                if (line.StartsWith("Chapter", StringComparison.OrdinalIgnoreCase))
-                {
-                    // Example:
-                    // Chapter #0.2: start 400.534, end 4565.435
-                    const string srch = "start ";
-                    var start = line.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
-
-                    if (start == -1)
-                    {
-                        continue;
-                    }
-
-                    var subString = line.Substring(start + srch.Length);
-                    subString = subString.Substring(0, subString.IndexOf(','));
-
-                    double seconds;
-
-                    if (double.TryParse(subString, out seconds))
-                    {
-                        lastChapter = new ChapterInfo
-                        {
-                            StartPositionTicks = TimeSpan.FromSeconds(seconds).Ticks
-                        };
-
-                        chapters.Add(lastChapter);
-                    }
-                }
-
-                else if (line.StartsWith("title", StringComparison.OrdinalIgnoreCase))
-                {
-                    if (lastChapter != null && string.IsNullOrEmpty(lastChapter.Name))
-                    {
-                        var index = line.IndexOf(':');
-
-                        if (index != -1)
-                        {
-                            lastChapter.Name = line.Substring(index + 1).Trim().TrimEnd('\r');
-                        }
-                    }
-                }
-            }
-
-            result.Chapters = chapters;
-        }
-
         /// <summary>
         /// The first chapter ticks
         /// </summary>
-        private static long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
+        private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
 
         /// <summary>
         /// Extracts the chapter images.
@@ -710,14 +191,17 @@ namespace MediaBrowser.Controller.MediaInfo
                         // Add some time for the first chapter to make sure we don't end up with a black image
                         var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
 
-                        var success = await ExtractImage(GetInputArgument(video), time, path, cancellationToken).ConfigureAwait(false);
+                        InputType type;
 
-                        if (success)
+                        var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+
+                        try
                         {
+                            await _encoder.ExtractImage(inputPath, type, time, path, cancellationToken).ConfigureAwait(false);
                             chapter.ImagePath = path;
                             changesMade = true;
                         }
-                        else
+                        catch
                         {
                             break;
                         }
@@ -736,70 +220,6 @@ namespace MediaBrowser.Controller.MediaInfo
             }
         }
 
-        /// <summary>
-        /// Extracts an image from an Audio file and returns a Task whose result indicates whether it was successful or not
-        /// </summary>
-        /// <param name="inputPath">The input path.</param>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">input</exception>
-        public async Task<bool> ExtractAudioImage(string inputPath, string outputPath, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(inputPath))
-            {
-                throw new ArgumentNullException("inputPath");
-            }
-
-            if (string.IsNullOrEmpty(outputPath))
-            {
-                throw new ArgumentNullException("outputPath");
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-                    FileName = FFMpegPath,
-                    Arguments = string.Format("-i {0} -threads 0 -v quiet -f image2 \"{1}\"", GetFileInputArgument(inputPath), outputPath),
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                }
-            };
-
-            await AudioImageResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            await RunAsync(process).ConfigureAwait(false);
-
-            AudioImageResourcePool.Release();
-
-            var exitCode = process.ExitCode;
-
-            process.Dispose();
-
-            if (exitCode != -1 && File.Exists(outputPath))
-            {
-                return true;
-            }
-
-            _logger.Error("ffmpeg audio image extraction failed for {0}", inputPath);
-            return false;
-        }
-
-        /// <summary>
-        /// Determines whether [is subtitle cached] [the specified input].
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="outputExtension">The output extension.</param>
-        /// <returns><c>true</c> if [is subtitle cached] [the specified input]; otherwise, <c>false</c>.</returns>
-        public bool IsSubtitleCached(Video input, int subtitleStreamIndex, string outputExtension)
-        {
-            return SubtitleCache.ContainsFilePath(GetSubtitleCachePath(input, subtitleStreamIndex, outputExtension));
-        }
-
         /// <summary>
         /// Gets the subtitle cache path.
         /// </summary>
@@ -811,364 +231,5 @@ namespace MediaBrowser.Controller.MediaInfo
         {
             return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks, outputExtension);
         }
-
-        /// <summary>
-        /// Extracts the text subtitle.
-        /// </summary>
-        /// <param name="input">The input.</param>
-        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">input</exception>
-        public async Task<bool> ExtractTextSubtitle(Video input, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
-        {
-            if (input == null)
-            {
-                throw new ArgumentNullException("input");
-            }
-
-            if (cancellationToken == null)
-            {
-                throw new ArgumentNullException("cancellationToken");
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-                    FileName = FFMpegPath,
-                    Arguments = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", GetInputArgument(input), subtitleStreamIndex, outputPath),
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                }
-            };
-
-            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
-            await AudioImageResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            await RunAsync(process).ConfigureAwait(false);
-
-            AudioImageResourcePool.Release();
-
-            var exitCode = process.ExitCode;
-
-            process.Dispose();
-
-            if (exitCode != -1 && File.Exists(outputPath))
-            {
-                return true;
-            }
-
-            _logger.Error("ffmpeg subtitle extraction failed for {0}", input.Path);
-            return false;
-        }
-
-        /// <summary>
-        /// Converts the text subtitle.
-        /// </summary>
-        /// <param name="mediaStream">The media stream.</param>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">mediaStream</exception>
-        /// <exception cref="System.ArgumentException">The given MediaStream is not an external subtitle stream</exception>
-        public async Task<bool> ConvertTextSubtitle(MediaStream mediaStream, string outputPath, CancellationToken cancellationToken)
-        {
-            if (mediaStream == null)
-            {
-                throw new ArgumentNullException("mediaStream");
-            }
-
-            if (!mediaStream.IsExternal || string.IsNullOrEmpty(mediaStream.Path))
-            {
-                throw new ArgumentException("The given MediaStream is not an external subtitle stream");
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-                    FileName = FFMpegPath,
-                    Arguments = string.Format("-i \"{0}\" \"{1}\"", mediaStream.Path, outputPath),
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                }
-            };
-
-            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
-            await AudioImageResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            await RunAsync(process).ConfigureAwait(false);
-
-            AudioImageResourcePool.Release();
-
-            var exitCode = process.ExitCode;
-
-            process.Dispose();
-
-            if (exitCode != -1 && File.Exists(outputPath))
-            {
-                return true;
-            }
-
-            _logger.Error("ffmpeg subtitle conversion failed for {0}", mediaStream.Path);
-            return false;
-        }
-
-        /// <summary>
-        /// Extracts an image from a Video and returns a Task whose result indicates whether it was successful or not
-        /// </summary>
-        /// <param name="inputPath">The input path.</param>
-        /// <param name="offset">The offset.</param>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">video</exception>
-        public async Task<bool> ExtractImage(string inputPath, TimeSpan offset, string outputPath, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrEmpty(inputPath))
-            {
-                throw new ArgumentNullException("inputPath");
-            }
-
-            if (string.IsNullOrEmpty(outputPath))
-            {
-                throw new ArgumentNullException("outputPath");
-            }
-
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    CreateNoWindow = true,
-                    UseShellExecute = false,
-                    FileName = FFMpegPath,
-                    Arguments = string.Format("-ss {0} -i {1} -threads 0 -v quiet -vframes 1 -filter:v select=\\'eq(pict_type\\,I)\\' -f image2 \"{2}\"", Convert.ToInt32(offset.TotalSeconds), inputPath, outputPath),
-                    WindowStyle = ProcessWindowStyle.Hidden,
-                    ErrorDialog = false
-                }
-            };
-
-            await VideoImageResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            process.Start();
-
-            var ranToCompletion = process.WaitForExit(10000);
-
-            if (!ranToCompletion)
-            {
-                try
-                {
-                    _logger.Info("Killing ffmpeg process");
-
-                    process.Kill();
-                    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);
-                }
-            }
-
-            VideoImageResourcePool.Release();
-
-            var exitCode = ranToCompletion ? process.ExitCode : -1;
-
-            process.Dispose();
-
-            if (exitCode == -1)
-            {
-                if (File.Exists(outputPath))
-                {
-                    try
-                    {
-                        _logger.Info("Deleting extracted image due to failure: ", outputPath);
-                        File.Delete(outputPath);
-                    }
-                    catch (IOException ex)
-                    {
-                        _logger.ErrorException("Error deleting extracted image {0}", ex, outputPath);
-                    }
-                }
-            }
-            else
-            {
-                if (File.Exists(outputPath))
-                {
-                    return true;
-                }
-            }
-
-            _logger.Error("ffmpeg video image extraction failed for {0}", inputPath);
-            return false;
-        }
-
-        /// <summary>
-        /// Gets the input argument.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetInputArgument(BaseItem item)
-        {
-            var video = item as Video;
-
-            if (video != null)
-            {
-                if (video.VideoType == VideoType.BluRay)
-                {
-                    return GetBlurayInputArgument(video.Path);
-                }
-
-                if (video.VideoType == VideoType.Dvd)
-                {
-                    return GetDvdInputArgument(video.GetPlayableStreamFiles());
-                }
-            }
-
-            return string.Format("file:\"{0}\"", item.Path);
-        }
-
-        /// <summary>
-        /// Gets the file input argument.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>System.String.</returns>
-        private string GetFileInputArgument(string path)
-        {
-            return string.Format("file:\"{0}\"", path);
-        }
-
-        /// <summary>
-        /// Gets the input argument.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="mount">The mount.</param>
-        /// <returns>System.String.</returns>
-        public string GetInputArgument(Video item, IIsoMount mount)
-        {
-            if (item.VideoType == VideoType.Iso && item.IsoType.HasValue)
-            {
-                if (item.IsoType.Value == IsoType.BluRay)
-                {
-                    return GetBlurayInputArgument(mount.MountedPath);
-                }
-                if (item.IsoType.Value == IsoType.Dvd)
-                {
-                    return GetDvdInputArgument(item.GetPlayableStreamFiles(mount.MountedPath));
-                }
-            }
-
-            return GetInputArgument(item);
-        }
-
-        /// <summary>
-        /// Gets the bluray input argument.
-        /// </summary>
-        /// <param name="blurayRoot">The bluray root.</param>
-        /// <returns>System.String.</returns>
-        public string GetBlurayInputArgument(string blurayRoot)
-        {
-            return string.Format("bluray:\"{0}\"", blurayRoot);
-        }
-
-        /// <summary>
-        /// Gets the DVD input argument.
-        /// </summary>
-        /// <param name="playableStreamFiles">The playable stream files.</param>
-        /// <returns>System.String.</returns>
-        public string GetDvdInputArgument(IEnumerable<string> playableStreamFiles)
-        {
-            // Get all streams
-            var streamFilePaths = (playableStreamFiles ?? new string[] { }).ToArray();
-
-            // If there's more than one we'll need to use the concat command
-            if (streamFilePaths.Length > 1)
-            {
-                var files = string.Join("|", streamFilePaths);
-
-                return string.Format("concat:\"{0}\"", files);
-            }
-
-            // Determine the input path for video files
-            return string.Format("file:\"{0}\"", streamFilePaths[0]);
-        }
-
-        /// <summary>
-        /// Processes the exited.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        void ProcessExited(object sender, EventArgs e)
-        {
-            ((Process)sender).Dispose();
-        }
-
-        /// <summary>
-        /// Provides a non-blocking method to start a process and wait asynchronously for it to exit
-        /// </summary>
-        /// <param name="process">The process.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        private static Task<bool> RunAsync(Process process)
-        {
-            var tcs = new TaskCompletionSource<bool>();
-
-            process.EnableRaisingEvents = true;
-            process.Exited += (sender, args) => tcs.SetResult(true);
-
-            process.Start();
-
-            return tcs.Task;
-        }
-
-        /// <summary>
-        /// Sets the error mode.
-        /// </summary>
-        /// <param name="uMode">The u mode.</param>
-        /// <returns>ErrorModes.</returns>
-        [DllImport("kernel32.dll")]
-        static extern ErrorModes SetErrorMode(ErrorModes uMode);
-
-        /// <summary>
-        /// Enum ErrorModes
-        /// </summary>
-        [Flags]
-        public enum ErrorModes : uint
-        {
-            /// <summary>
-            /// The SYSTE m_ DEFAULT
-            /// </summary>
-            SYSTEM_DEFAULT = 0x0,
-            /// <summary>
-            /// The SE m_ FAILCRITICALERRORS
-            /// </summary>
-            SEM_FAILCRITICALERRORS = 0x0001,
-            /// <summary>
-            /// The SE m_ NOALIGNMENTFAULTEXCEPT
-            /// </summary>
-            SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
-            /// <summary>
-            /// The SE m_ NOGPFAULTERRORBOX
-            /// </summary>
-            SEM_NOGPFAULTERRORBOX = 0x0002,
-            /// <summary>
-            /// The SE m_ NOOPENFILEERRORBOX
-            /// </summary>
-            SEM_NOOPENFILEERRORBOX = 0x8000
-        }
     }
 }

+ 3 - 4
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -1,13 +1,12 @@
-using System.Collections.Generic;
-using MediaBrowser.Controller.Entities;
-using System;
+using MediaBrowser.Controller.Entities;
+using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Providers
 {
-    public interface IProviderManager : IDisposable
+    public interface IProviderManager
     {
         /// <summary>
         /// Downloads the and save image.

+ 6 - 2
MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
@@ -15,8 +16,11 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
     public abstract class BaseFFMpegProvider<T> : BaseMetadataProvider
         where T : BaseItem
     {
-        protected BaseFFMpegProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager)
+        protected readonly IMediaEncoder MediaEncoder;
+
+        protected BaseFFMpegProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder) : base(logManager, configurationManager)
         {
+            MediaEncoder = mediaEncoder;
         }
 
         /// <summary>
@@ -53,7 +57,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         {
             get
             {
-                return Kernel.Instance.FFMpegManager.FFMpegVersion;
+                return MediaEncoder.Version;
             }
         }
 

+ 69 - 30
MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs

@@ -1,12 +1,13 @@
-using System.Globalization;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
@@ -17,13 +18,17 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
     /// Provides a base class for extracting media information through ffprobe
     /// </summary>
     /// <typeparam name="T"></typeparam>
-    public abstract class BaseFFProbeProvider<T> : BaseFFMpegProvider<T>, IDisposable
+    public abstract class BaseFFProbeProvider<T> : BaseFFMpegProvider<T>
         where T : BaseItem
     {
-        protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager)
+        protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IProtobufSerializer protobufSerializer)
+            : base(logManager, configurationManager, mediaEncoder)
         {
+            ProtobufSerializer = protobufSerializer;
         }
 
+        protected readonly IProtobufSerializer ProtobufSerializer;
+        
         /// <summary>
         /// Gets or sets the FF probe cache.
         /// </summary>
@@ -81,11 +86,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
             {
                 OnPreFetch(myItem, isoMount);
 
-                var inputPath = isoMount == null ? 
-                    Kernel.Instance.FFMpegManager.GetInputArgument(myItem) :
-                    Kernel.Instance.FFMpegManager.GetInputArgument((Video)item, isoMount);
-
-                var result = await Kernel.Instance.FFMpegManager.RunFFProbe(item, inputPath, item.DateModified, FFProbeCache, cancellationToken).ConfigureAwait(false);
+                var result = await GetMediaInfo(item, isoMount, item.DateModified, FFProbeCache, cancellationToken).ConfigureAwait(false);
 
                 cancellationToken.ThrowIfCancellationRequested();
 
@@ -110,6 +111,61 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
             return true;
         }
 
+        /// <summary>
+        /// Gets the media info.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="isoMount">The iso mount.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cache">The cache.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{MediaInfoResult}.</returns>
+        /// <exception cref="System.ArgumentNullException">inputPath
+        /// or
+        /// cache</exception>
+        private async Task<MediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, DateTime lastDateModified, FileSystemRepository cache, CancellationToken cancellationToken)
+        {
+            if (cache == null)
+            {
+                throw new ArgumentNullException("cache");
+            }
+
+            // Put the ffmpeg version into the cache name so that it's unique per-version
+            // We don't want to try and deserialize data based on an old version, which could potentially fail
+            var resourceName = item.Id + "_" + lastDateModified.Ticks + "_" + MediaEncoder.Version;
+
+            // Forumulate the cache file path
+            var cacheFilePath = cache.GetResourcePath(resourceName, ".pb");
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            // Avoid File.Exists by just trying to deserialize
+            try
+            {
+                return ProtobufSerializer.DeserializeFromFile<MediaInfoResult>(cacheFilePath);
+            }
+            catch (FileNotFoundException)
+            {
+                // Cache file doesn't exist
+            }
+
+            var type = InputType.AudioFile;
+            var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath };
+
+            var video = item as Video;
+
+            if (video != null)
+            {
+                inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+            }
+
+            var info = await MediaEncoder.GetMediaInfo(inputPath, type, cancellationToken).ConfigureAwait(false);
+
+            ProtobufSerializer.SerializeToFile(info, cacheFilePath);
+
+            return info;
+        }
+
         /// <summary>
         /// Gets a value indicating whether [refresh on version change].
         /// </summary>
@@ -147,7 +203,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// Normalizes the FF probe result.
         /// </summary>
         /// <param name="result">The result.</param>
-        private void NormalizeFFProbeResult(FFProbeResult result)
+        private void NormalizeFFProbeResult(MediaInfoResult result)
         {
             if (result.format != null && result.format.tags != null)
             {
@@ -180,7 +236,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="result">The result.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected abstract void Fetch(T item, CancellationToken cancellationToken, FFProbeResult result, IIsoMount isoMount);
+        protected abstract void Fetch(T item, CancellationToken cancellationToken, MediaInfoResult result, IIsoMount isoMount);
 
         /// <summary>
         /// Converts ffprobe stream info to our MediaStream class
@@ -188,7 +244,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="streamInfo">The stream info.</param>
         /// <param name="formatInfo">The format info.</param>
         /// <returns>MediaStream.</returns>
-        protected MediaStream GetMediaStream(FFProbeMediaStreamInfo streamInfo, FFProbeMediaFormatInfo formatInfo)
+        protected MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
         {
             var stream = new MediaStream
             {
@@ -360,22 +416,5 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         {
             return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
         }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                FFProbeCache.Dispose();
-            }
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-        }
     }
 }

+ 9 - 9
MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs

@@ -1,10 +1,11 @@
-using System.Collections.Concurrent;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
+using System.Collections.Concurrent;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -16,8 +17,8 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
     /// </summary>
     public class FFMpegAudioImageProvider : BaseFFMpegProvider<Audio>
     {
-        public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
+        public FFMpegAudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
+            : base(logManager, configurationManager, mediaEncoder)
         {
         }
 
@@ -94,12 +95,11 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
                             {
                                 try
                                 {
-                                    var imageSucceeded = await Kernel.Instance.FFMpegManager.ExtractAudioImage(audio.Path, path, cancellationToken).ConfigureAwait(false);
-
-                                    if (!imageSucceeded)
-                                    {
-                                        success = ProviderRefreshStatus.Failure;
-                                    }
+                                    await MediaEncoder.ExtractImage(new[] { audio.Path }, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false);
+                                }
+                                catch
+                                {
+                                    success = ProviderRefreshStatus.Failure;
                                 }
                                 finally
                                 {

+ 22 - 23
MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs

@@ -1,10 +1,11 @@
-using System.Linq;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -26,12 +27,12 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        public FfMpegVideoImageProvider(IIsoManager isoManager, ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
+        public FfMpegVideoImageProvider(IIsoManager isoManager, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
+            : base(logManager, configurationManager, mediaEncoder)
         {
             _isoManager = isoManager;
-        }        
-        
+        }
+
         /// <summary>
         /// Gets the priority.
         /// </summary>
@@ -57,7 +58,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
             if (video != null)
             {
-                if (video.VideoType == VideoType.Iso && video.IsoType.HasValue && _isoManager.CanMount(item.Path))
+                if (video.VideoType == VideoType.Iso && _isoManager.CanMount(item.Path))
                 {
                     return true;
                 }
@@ -139,25 +140,23 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
             {
                 // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
                 // Always use 10 seconds for dvd because our duration could be out of whack
-                var imageOffset = video.VideoType != VideoType.Dvd && video.RunTimeTicks.HasValue && video.RunTimeTicks.Value > 0
-                                           ? TimeSpan.FromTicks(Convert.ToInt64(video.RunTimeTicks.Value * .1))
-                                           : TimeSpan.FromSeconds(10);
+                var imageOffset = video.VideoType != VideoType.Dvd && video.RunTimeTicks.HasValue &&
+                                  video.RunTimeTicks.Value > 0
+                                      ? TimeSpan.FromTicks(Convert.ToInt64(video.RunTimeTicks.Value * .1))
+                                      : TimeSpan.FromSeconds(10);
 
-                var inputPath = isoMount == null ?
-                    Kernel.Instance.FFMpegManager.GetInputArgument(video) :
-                    Kernel.Instance.FFMpegManager.GetInputArgument(video, isoMount);
+                InputType type;
 
-                var success = await Kernel.Instance.FFMpegManager.ExtractImage(inputPath, imageOffset, path, cancellationToken).ConfigureAwait(false);
+                var inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
 
-                if (success)
-                {
-                    video.PrimaryImagePath = path;
-                    SetLastRefreshed(video, DateTime.UtcNow);
-                }
-                else
-                {
-                    SetLastRefreshed(video, DateTime.UtcNow, ProviderRefreshStatus.Failure);
-                }
+                await MediaEncoder.ExtractImage(inputPath, type, imageOffset, path, cancellationToken).ConfigureAwait(false);
+
+                video.PrimaryImagePath = path;
+                SetLastRefreshed(video, DateTime.UtcNow);
+            }
+            catch
+            {
+                SetLastRefreshed(video, DateTime.UtcNow, ProviderRefreshStatus.Failure);
             }
             finally
             {

+ 6 - 5
MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs

@@ -1,15 +1,15 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Controller.Providers.MediaInfo
 {
@@ -18,7 +18,8 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
     /// </summary>
     public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
     {
-        public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager)
+        public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IProtobufSerializer protobufSerializer)
+            : base(logManager, configurationManager, mediaEncoder, protobufSerializer)
         {
         }
 
@@ -42,7 +43,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="data">The data.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected override void Fetch(Audio audio, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
+        protected override void Fetch(Audio audio, CancellationToken cancellationToken, MediaInfoResult data, IIsoMount isoMount)
         {
             if (data.streams == null)
             {

+ 6 - 26
MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs

@@ -1,8 +1,8 @@
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
@@ -21,8 +21,8 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
     /// </summary>
     public class FFProbeVideoInfoProvider : BaseFFProbeProvider<Video>
     {
-        public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IProtobufSerializer protobufSerializer, ILogManager logManager, IServerConfigurationManager configurationManager)
-            : base(logManager, configurationManager)
+        public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IProtobufSerializer protobufSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
+            : base(logManager, configurationManager, mediaEncoder, protobufSerializer)
         {
             if (isoManager == null)
             {
@@ -39,7 +39,6 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
             _blurayExaminer = blurayExaminer;
             _isoManager = isoManager;
-            _protobufSerializer = protobufSerializer;
 
             BdInfoCache = new FileSystemRepository(Path.Combine(ConfigurationManager.ApplicationPaths.CachePath, "bdinfo"));
         }
@@ -61,11 +60,6 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// </summary>
         private readonly IIsoManager _isoManager;
 
-        /// <summary>
-        /// The _protobuf serializer
-        /// </summary>
-        private readonly IProtobufSerializer _protobufSerializer;
-
         /// <summary>
         /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
         /// </summary>
@@ -187,7 +181,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="data">The data.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected override void Fetch(Video video, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
+        protected override void Fetch(Video video, CancellationToken cancellationToken, MediaInfoResult data, IIsoMount isoMount)
         {
             if (data.format != null)
             {
@@ -335,13 +329,13 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
             try
             {
-                result = _protobufSerializer.DeserializeFromFile<BlurayDiscInfo>(cacheFile);
+                result = ProtobufSerializer.DeserializeFromFile<BlurayDiscInfo>(cacheFile);
             }
             catch (FileNotFoundException)
             {
                 result = GetBDInfo(inputPath);
 
-                _protobufSerializer.SerializeToFile(result, cacheFile);
+                ProtobufSerializer.SerializeToFile(result, cacheFile);
             }
 
             cancellationToken.ThrowIfCancellationRequested();
@@ -422,19 +416,5 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         {
             return _blurayExaminer.GetDiscInfo(path);
         }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected override void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                BdInfoCache.Dispose();
-            }
-
-            base.Dispose(dispose);
-        }
     }
 }

+ 96 - 0
MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs

@@ -0,0 +1,96 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Providers.MediaInfo
+{
+    /// <summary>
+    /// Class MediaEncoderHelpers
+    /// </summary>
+    public static class MediaEncoderHelpers
+    {
+        /// <summary>
+        /// Gets the input argument.
+        /// </summary>
+        /// <param name="video">The video.</param>
+        /// <param name="isoMount">The iso mount.</param>
+        /// <param name="type">The type.</param>
+        /// <returns>System.String[][].</returns>
+        public static string[] GetInputArgument(Video video, IIsoMount isoMount, out InputType type)
+        {
+            var inputPath = isoMount == null ? new[] { video.Path } : new[] { isoMount.MountedPath };
+
+            type = InputType.VideoFile;
+
+            switch (video.VideoType)
+            {
+                case VideoType.BluRay:
+                    type = InputType.Bluray;
+                    break;
+                case VideoType.Dvd:
+                    type = InputType.Dvd;
+                    inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+                    break;
+                case VideoType.Iso:
+                    if (video.IsoType.HasValue)
+                    {
+                        switch (video.IsoType.Value)
+                        {
+                            case IsoType.BluRay:
+                                type = InputType.Bluray;
+                                break;
+                            case IsoType.Dvd:
+                                type = InputType.Dvd;
+                                inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+                                break;
+                        }
+                    }
+                    break;
+            }
+
+            return inputPath;
+        }
+
+        /// <summary>
+        /// Gets the type of the input.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>InputType.</returns>
+        public static InputType GetInputType(BaseItem item)
+        {
+            var type = InputType.AudioFile;
+
+            var video = item as Video;
+
+            if (video != null)
+            {
+                switch (video.VideoType)
+                {
+                    case VideoType.BluRay:
+                        type = InputType.Bluray;
+                        break;
+                    case VideoType.Dvd:
+                        type = InputType.Dvd;
+                        break;
+                    case VideoType.Iso:
+                        if (video.IsoType.HasValue)
+                        {
+                            switch (video.IsoType.Value)
+                            {
+                                case IsoType.BluRay:
+                                    type = InputType.Bluray;
+                                    break;
+                                case IsoType.Dvd:
+                                    type = InputType.Dvd;
+                                    break;
+                            }
+                        }
+                        break;
+                }
+            }
+
+            return type;
+        }
+    }
+}

+ 13 - 2
MediaBrowser.Model/Querying/ItemQuery.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using System;
 
 namespace MediaBrowser.Model.Querying
@@ -141,6 +140,18 @@ namespace MediaBrowser.Model.Querying
         /// <value>The image types.</value>
         public ImageType[] ImageTypes { get; set; }
 
+        /// <summary>
+        /// Gets or sets the air days.
+        /// </summary>
+        /// <value>The air days.</value>
+        public DayOfWeek[] AirDays { get; set; }
+
+        /// <summary>
+        /// Gets or sets the series status.
+        /// </summary>
+        /// <value>The series status.</value>
+        public SeriesStatus[] SeriesStatuses { get; set; }
+        
         /// <summary>
         /// Gets or sets the ids, which are specific items to retrieve
         /// </summary>

+ 0 - 1
MediaBrowser.Model/packages.config

@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="PropertyChanged.Fody" version="1.32.2.0" targetFramework="portable-win+net45+sl40+wp" />
-  <package id="protobuf-net" version="2.0.0.621" targetFramework="portable-win+net45+sl40+wp" />
 </packages>

+ 5 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -147,6 +147,7 @@
     <Compile Include="Library\Resolvers\TV\SeriesResolver.cs" />
     <Compile Include="Library\Resolvers\VideoResolver.cs" />
     <Compile Include="Library\UserManager.cs" />
+    <Compile Include="MediaEncoder\MediaEncoder.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Providers\ProviderManager.cs" />
     <Compile Include="Reflection\TypeMapper.cs" />
@@ -202,6 +203,7 @@
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
+    <EmbeddedResource Include="MediaEncoder\readme.txt" />
     <Content Include="README.txt" />
     <Content Include="swagger-ui\css\screen.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -253,6 +255,9 @@
     </Content>
   </ItemGroup>
   <ItemGroup>
+    <EmbeddedResource Include="MediaEncoder\ffmpeg20130405.zip" />
+    <EmbeddedResource Include="MediaEncoder\fonts\ARIALUNI.TTF" />
+    <EmbeddedResource Include="MediaEncoder\fonts\fonts.conf" />
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup />

+ 930 - 0
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -0,0 +1,930 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.MediaEncoder
+{
+    /// <summary>
+    /// Class MediaEncoder
+    /// </summary>
+    public class MediaEncoder : IMediaEncoder, IDisposable
+    {
+        /// <summary>
+        /// Gets or sets the zip client.
+        /// </summary>
+        /// <value>The zip client.</value>
+        private readonly IZipClient _zipClient;
+
+        /// <summary>
+        /// The _logger
+        /// </summary>
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// The _app paths
+        /// </summary>
+        private readonly IApplicationPaths _appPaths;
+
+        /// <summary>
+        /// Gets the json serializer.
+        /// </summary>
+        /// <value>The json serializer.</value>
+        private readonly IJsonSerializer _jsonSerializer;
+
+        /// <summary>
+        /// The video image resource pool
+        /// </summary>
+        private readonly SemaphoreSlim _videoImageResourcePool = new SemaphoreSlim(2, 2);
+
+        /// <summary>
+        /// The audio image resource pool
+        /// </summary>
+        private readonly SemaphoreSlim _audioImageResourcePool = new SemaphoreSlim(3, 3);
+
+        /// <summary>
+        /// The _subtitle extraction resource pool
+        /// </summary>
+        private readonly SemaphoreSlim _subtitleExtractionResourcePool = new SemaphoreSlim(2, 2);
+
+        /// <summary>
+        /// The FF probe resource pool
+        /// </summary>
+        private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(3, 3);
+
+        /// <summary>
+        /// Gets or sets the versioned directory path.
+        /// </summary>
+        /// <value>The versioned directory path.</value>
+        private string VersionedDirectoryPath { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="MediaEncoder" /> class.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        /// <param name="zipClient">The zip client.</param>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="jsonSerializer">The json serializer.</param>
+        public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths, IJsonSerializer jsonSerializer)
+        {
+            _logger = logger;
+            _zipClient = zipClient;
+            _appPaths = appPaths;
+            _jsonSerializer = jsonSerializer;
+
+            // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes
+            SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX);
+
+            Task.Run(() => VersionedDirectoryPath = GetVersionedDirectoryPath());
+        }
+
+        /// <summary>
+        /// The _media tools path
+        /// </summary>
+        private string _mediaToolsPath;
+        /// <summary>
+        /// Gets the folder path to tools
+        /// </summary>
+        /// <value>The media tools path.</value>
+        private string MediaToolsPath
+        {
+            get
+            {
+                if (_mediaToolsPath == null)
+                {
+                    _mediaToolsPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
+
+                    if (!Directory.Exists(_mediaToolsPath))
+                    {
+                        Directory.CreateDirectory(_mediaToolsPath);
+                    }
+                }
+
+                return _mediaToolsPath;
+            }
+        }
+
+        /// <summary>
+        /// Gets the encoder path.
+        /// </summary>
+        /// <value>The encoder path.</value>
+        public string EncoderPath
+        {
+            get { return FFMpegPath; }
+        }
+
+        /// <summary>
+        /// The _ FF MPEG path
+        /// </summary>
+        private string _FFMpegPath;
+        /// <summary>
+        /// Gets the path to ffmpeg.exe
+        /// </summary>
+        /// <value>The FF MPEG path.</value>
+        public string FFMpegPath
+        {
+            get
+            {
+                return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe"));
+            }
+        }
+
+        /// <summary>
+        /// The _ FF probe path
+        /// </summary>
+        private string _FFProbePath;
+        /// <summary>
+        /// Gets the path to ffprobe.exe
+        /// </summary>
+        /// <value>The FF probe path.</value>
+        private string FFProbePath
+        {
+            get
+            {
+                return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe"));
+            }
+        }
+
+        /// <summary>
+        /// Gets the version.
+        /// </summary>
+        /// <value>The version.</value>
+        public string Version
+        {
+            get { return Path.GetFileNameWithoutExtension(VersionedDirectoryPath); }
+        }
+
+        /// <summary>
+        /// Gets the versioned directory path.
+        /// </summary>
+        /// <returns>System.String.</returns>
+        private string GetVersionedDirectoryPath()
+        {
+            var assembly = GetType().Assembly;
+
+            var prefix = GetType().Namespace + ".";
+
+            var srch = prefix + "ffmpeg";
+
+            var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch));
+
+            var filename = resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length);
+
+            var versionedDirectoryPath = Path.Combine(MediaToolsPath, Path.GetFileNameWithoutExtension(filename));
+
+            if (!Directory.Exists(versionedDirectoryPath))
+            {
+                Directory.CreateDirectory(versionedDirectoryPath);
+            }
+
+            ExtractTools(assembly, resource, versionedDirectoryPath);
+
+            return versionedDirectoryPath;
+        }
+
+        /// <summary>
+        /// Extracts the tools.
+        /// </summary>
+        /// <param name="assembly">The assembly.</param>
+        /// <param name="zipFileResourcePath">The zip file resource path.</param>
+        /// <param name="targetPath">The target path.</param>
+        private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
+        {
+            using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
+            {
+                _zipClient.ExtractAll(resourceStream, targetPath, false);
+            }
+
+            ExtractFonts(assembly, targetPath);
+        }
+
+        /// <summary>
+        /// Extracts the fonts.
+        /// </summary>
+        /// <param name="assembly">The assembly.</param>
+        /// <param name="targetPath">The target path.</param>
+        private async void ExtractFonts(Assembly assembly, string targetPath)
+        {
+            var fontsDirectory = Path.Combine(targetPath, "fonts");
+
+            if (!Directory.Exists(fontsDirectory))
+            {
+                Directory.CreateDirectory(fontsDirectory);
+            }
+
+            const string fontFilename = "ARIALUNI.TTF";
+
+            var fontFile = Path.Combine(fontsDirectory, fontFilename);
+
+            if (!File.Exists(fontFile))
+            {
+                using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontFilename))
+                {
+                    using (var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                    {
+                        await stream.CopyToAsync(fileStream).ConfigureAwait(false);
+                    }
+                }
+            }
+
+            await ExtractFontConfigFile(assembly, fontsDirectory).ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// Extracts the font config file.
+        /// </summary>
+        /// <param name="assembly">The assembly.</param>
+        /// <param name="fontsDirectory">The fonts directory.</param>
+        /// <returns>Task.</returns>
+        private async Task ExtractFontConfigFile(Assembly assembly, string fontsDirectory)
+        {
+            const string fontConfigFilename = "fonts.conf";
+            var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
+
+            if (!File.Exists(fontConfigFile))
+            {
+                using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontConfigFilename))
+                {
+                    using (var streamReader = new StreamReader(stream))
+                    {
+                        var contents = await streamReader.ReadToEndAsync().ConfigureAwait(false);
+
+                        contents = contents.Replace("<dir></dir>", "<dir>" + fontsDirectory + "</dir>");
+
+                        var bytes = Encoding.UTF8.GetBytes(contents);
+
+                        using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                        {
+                            await fileStream.WriteAsync(bytes, 0, bytes.Length);
+                        }
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the media info.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task<MediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken)
+        {
+            return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.AudioFile, GetProbeSizeArgument(type), cancellationToken);
+        }
+
+        /// <summary>
+        /// Gets the input argument.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
+        public string GetInputArgument(string[] inputFiles, InputType type)
+        {
+            string inputPath = null;
+
+            switch (type)
+            {
+                case InputType.Dvd:
+                case InputType.VideoFile:
+                case InputType.AudioFile:
+                    inputPath = GetConcatInputArgument(inputFiles);
+                    break;
+                case InputType.Bluray:
+                    inputPath = GetBlurayInputArgument(inputFiles[0]);
+                    break;
+                default:
+                    throw new ArgumentException("Unrecognized InputType");
+            }
+
+            return inputPath;
+        }
+
+        /// <summary>
+        /// Gets the probe size argument.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>System.String.</returns>
+        public string GetProbeSizeArgument(InputType type)
+        {
+            return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+        }
+
+        /// <summary>
+        /// Gets the media info internal.
+        /// </summary>
+        /// <param name="inputPath">The input path.</param>
+        /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
+        /// <param name="probeSizeArgument">The probe size argument.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{MediaInfoResult}.</returns>
+        /// <exception cref="System.ApplicationException"></exception>
+        private async Task<MediaInfoResult> GetMediaInfoInternal(string inputPath, bool extractChapters, string probeSizeArgument, CancellationToken cancellationToken)
+        {
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+
+                    // Must consume both or ffmpeg may hang due to deadlocks. See comments below.   
+                    RedirectStandardOutput = true,
+                    RedirectStandardError = true,
+                    FileName = FFProbePath,
+                    Arguments = string.Format("{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format", probeSizeArgument, inputPath).Trim(),
+
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false
+                },
+
+                EnableRaisingEvents = true
+            };
+
+            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+
+            process.Exited += ProcessExited;
+
+            await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            MediaInfoResult result;
+            string standardError = null;
+
+            try
+            {
+                process.Start();
+
+                Task<string> standardErrorReadTask = null;
+
+                // MUST read both stdout and stderr asynchronously or a deadlock may occurr
+                if (extractChapters)
+                {
+                    standardErrorReadTask = process.StandardError.ReadToEndAsync();
+                }
+                else
+                {
+                    process.BeginErrorReadLine();
+                }
+
+                result = _jsonSerializer.DeserializeFromStream<MediaInfoResult>(process.StandardOutput.BaseStream);
+
+                if (extractChapters)
+                {
+                    standardError = await standardErrorReadTask.ConfigureAwait(false);
+                }
+            }
+            catch
+            {
+                // Hate having to do this
+                try
+                {
+                    process.Kill();
+                }
+                catch (InvalidOperationException ex1)
+                {
+                    _logger.ErrorException("Error killing ffprobe", ex1);
+                }
+                catch (Win32Exception ex1)
+                {
+                    _logger.ErrorException("Error killing ffprobe", ex1);
+                }
+
+                throw;
+            }
+            finally
+            {
+                _ffProbeResourcePool.Release();
+            }
+
+            if (result == null)
+            {
+                throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath));
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            if (extractChapters && !string.IsNullOrEmpty(standardError))
+            {
+                AddChapters(result, standardError);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Adds the chapters.
+        /// </summary>
+        /// <param name="result">The result.</param>
+        /// <param name="standardError">The standard error.</param>
+        private void AddChapters(MediaInfoResult result, string standardError)
+        {
+            var lines = standardError.Split('\n').Select(l => l.TrimStart());
+
+            var chapters = new List<ChapterInfo> { };
+
+            ChapterInfo lastChapter = null;
+
+            foreach (var line in lines)
+            {
+                if (line.StartsWith("Chapter", StringComparison.OrdinalIgnoreCase))
+                {
+                    // Example:
+                    // Chapter #0.2: start 400.534, end 4565.435
+                    const string srch = "start ";
+                    var start = line.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
+
+                    if (start == -1)
+                    {
+                        continue;
+                    }
+
+                    var subString = line.Substring(start + srch.Length);
+                    subString = subString.Substring(0, subString.IndexOf(','));
+
+                    double seconds;
+
+                    if (double.TryParse(subString, out seconds))
+                    {
+                        lastChapter = new ChapterInfo
+                        {
+                            StartPositionTicks = TimeSpan.FromSeconds(seconds).Ticks
+                        };
+
+                        chapters.Add(lastChapter);
+                    }
+                }
+
+                else if (line.StartsWith("title", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (lastChapter != null && string.IsNullOrEmpty(lastChapter.Name))
+                    {
+                        var index = line.IndexOf(':');
+
+                        if (index != -1)
+                        {
+                            lastChapter.Name = line.Substring(index + 1).Trim().TrimEnd('\r');
+                        }
+                    }
+                }
+            }
+
+            result.Chapters = chapters;
+        }
+
+        /// <summary>
+        /// Processes the exited.
+        /// </summary>
+        /// <param name="sender">The sender.</param>
+        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+        void ProcessExited(object sender, EventArgs e)
+        {
+            ((Process)sender).Dispose();
+        }
+
+        /// <summary>
+        /// Converts the text subtitle to ass.
+        /// </summary>
+        /// <param name="inputPath">The input path.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">inputPath
+        /// or
+        /// outputPath</exception>
+        /// <exception cref="System.ApplicationException"></exception>
+        public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(inputPath))
+            {
+                throw new ArgumentNullException("inputPath");
+            }
+
+            if (string.IsNullOrEmpty(outputPath))
+            {
+                throw new ArgumentNullException("outputPath");
+            }
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = FFMpegPath,
+                    Arguments = string.Format("-i \"{0}\" \"{1}\"", inputPath, outputPath),
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false
+                }
+            };
+
+            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+
+            await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            var ranToCompletion = StartAndWaitForProcess(process);
+
+            _subtitleExtractionResourcePool.Release();
+
+            var exitCode = ranToCompletion ? process.ExitCode : -1;
+
+            process.Dispose();
+
+            var failed = false;
+
+            if (exitCode == -1)
+            {
+                failed = true;
+
+                if (File.Exists(outputPath))
+                {
+                    try
+                    {
+                        _logger.Info("Deleting converted subtitle due to failure: ", outputPath);
+                        File.Delete(outputPath);
+                    }
+                    catch (IOException ex)
+                    {
+                        _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath);
+                    }
+                }
+            }
+            else if (!File.Exists(outputPath))
+            {
+                failed = true;
+            }
+
+            if (failed)
+            {
+                var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath);
+
+                _logger.Error(msg);
+
+                throw new ApplicationException(msg);
+            }
+        }
+
+        /// <summary>
+        /// Extracts the text subtitle.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
+        public Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
+        {
+            return ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, outputPath, cancellationToken);
+        }
+
+        /// <summary>
+        /// Extracts the text subtitle.
+        /// </summary>
+        /// <param name="inputPath">The input path.</param>
+        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">inputPath
+        /// or
+        /// outputPath
+        /// or
+        /// cancellationToken</exception>
+        /// <exception cref="System.ApplicationException"></exception>
+        private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(inputPath))
+            {
+                throw new ArgumentNullException("inputPath");
+            }
+
+            if (string.IsNullOrEmpty(outputPath))
+            {
+                throw new ArgumentNullException("outputPath");
+            }
+
+            if (cancellationToken == null)
+            {
+                throw new ArgumentNullException("cancellationToken");
+            }
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = FFMpegPath,
+                    Arguments = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, subtitleStreamIndex, outputPath),
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false
+                }
+            };
+
+            _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+
+            await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            var ranToCompletion = StartAndWaitForProcess(process);
+
+            _subtitleExtractionResourcePool.Release();
+
+            var exitCode = ranToCompletion ? process.ExitCode : -1;
+
+            process.Dispose();
+
+            var failed = false;
+
+            if (exitCode == -1)
+            {
+                failed = true;
+
+                if (File.Exists(outputPath))
+                {
+                    try
+                    {
+                        _logger.Info("Deleting extracted subtitle due to failure: ", outputPath);
+                        File.Delete(outputPath);
+                    }
+                    catch (IOException ex)
+                    {
+                        _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath);
+                    }
+                }
+            }
+            else if (!File.Exists(outputPath))
+            {
+                failed = true;
+            }
+
+            if (failed)
+            {
+                var msg = string.Format("ffmpeg subtitle extraction failed for {0}", inputPath);
+
+                _logger.Error(msg);
+
+                throw new ApplicationException(msg);
+            }
+        }
+
+        /// <summary>
+        /// Extracts the image.
+        /// </summary>
+        /// <param name="inputFiles">The input files.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="offset">The offset.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
+        public Task ExtractImage(string[] inputFiles, InputType type, TimeSpan? offset, string outputPath, CancellationToken cancellationToken)
+        {
+            var resourcePool = type == InputType.AudioFile ? _audioImageResourcePool : _videoImageResourcePool;
+
+            return ExtractImageInternal(GetInputArgument(inputFiles, type), offset, outputPath, resourcePool, cancellationToken);
+        }
+
+        /// <summary>
+        /// Extracts the image.
+        /// </summary>
+        /// <param name="inputPath">The input path.</param>
+        /// <param name="offset">The offset.</param>
+        /// <param name="outputPath">The output path.</param>
+        /// <param name="resourcePool">The resource pool.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException">inputPath
+        /// or
+        /// outputPath</exception>
+        /// <exception cref="System.ApplicationException"></exception>
+        private async Task ExtractImageInternal(string inputPath, TimeSpan? offset, string outputPath, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(inputPath))
+            {
+                throw new ArgumentNullException("inputPath");
+            }
+
+            if (string.IsNullOrEmpty(outputPath))
+            {
+                throw new ArgumentNullException("outputPath");
+            }
+
+
+            var args = string.Format("-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\\'eq(pict_type\\,I)\\' -f image2 \"{1}\"", inputPath, outputPath);
+
+            if (offset.HasValue)
+            {
+                args = string.Format("-ss {0} ", Convert.ToInt32(offset.Value.TotalSeconds)) + args;
+            }
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = FFMpegPath,
+                    Arguments = args,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false
+                }
+            };
+
+            await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            var ranToCompletion = StartAndWaitForProcess(process);
+
+            resourcePool.Release();
+
+            var exitCode = ranToCompletion ? process.ExitCode : -1;
+
+            process.Dispose();
+
+            var failed = false;
+
+            if (exitCode == -1)
+            {
+                failed = true;
+
+                if (File.Exists(outputPath))
+                {
+                    try
+                    {
+                        _logger.Info("Deleting extracted image due to failure: ", outputPath);
+                        File.Delete(outputPath);
+                    }
+                    catch (IOException ex)
+                    {
+                        _logger.ErrorException("Error deleting extracted image {0}", ex, outputPath);
+                    }
+                }
+            }
+            else if (!File.Exists(outputPath))
+            {
+                failed = true;
+            }
+
+            if (failed)
+            {
+                var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
+
+                _logger.Error(msg);
+
+                throw new ApplicationException(msg);
+            }
+        }
+
+        /// <summary>
+        /// Starts the and wait for process.
+        /// </summary>
+        /// <param name="process">The process.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        private bool StartAndWaitForProcess(Process process)
+        {
+            process.Start();
+
+            var ranToCompletion = process.WaitForExit(10000);
+
+            if (!ranToCompletion)
+            {
+                try
+                {
+                    _logger.Info("Killing ffmpeg process");
+
+                    process.Kill();
+
+                    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);
+                }
+            }
+
+            return ranToCompletion;
+        }
+
+        /// <summary>
+        /// Gets the file input argument.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>System.String.</returns>
+        public string GetFileInputArgument(string path)
+        {
+            return string.Format("file:\"{0}\"", path);
+        }
+
+        /// <summary>
+        /// Gets the concat input argument.
+        /// </summary>
+        /// <param name="playableStreamFiles">The playable stream files.</param>
+        /// <returns>System.String.</returns>
+        public string GetConcatInputArgument(string[] playableStreamFiles)
+        {
+            // Get all streams
+            // If there's more than one we'll need to use the concat command
+            if (playableStreamFiles.Length > 1)
+            {
+                var files = string.Join("|", playableStreamFiles);
+
+                return string.Format("concat:\"{0}\"", files);
+            }
+
+            // Determine the input path for video files
+            return string.Format("file:\"{0}\"", playableStreamFiles[0]);
+        }
+
+        /// <summary>
+        /// Gets the bluray input argument.
+        /// </summary>
+        /// <param name="blurayRoot">The bluray root.</param>
+        /// <returns>System.String.</returns>
+        public string GetBlurayInputArgument(string blurayRoot)
+        {
+            return string.Format("bluray:\"{0}\"", blurayRoot);
+        }
+
+        /// <summary>
+        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        /// <summary>
+        /// Releases unmanaged and - optionally - managed resources.
+        /// </summary>
+        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool dispose)
+        {
+            if (dispose)
+            {
+                _videoImageResourcePool.Dispose();
+            }
+
+            SetErrorMode(ErrorModes.SYSTEM_DEFAULT);
+        }
+
+        /// <summary>
+        /// Sets the error mode.
+        /// </summary>
+        /// <param name="uMode">The u mode.</param>
+        /// <returns>ErrorModes.</returns>
+        [DllImport("kernel32.dll")]
+        static extern ErrorModes SetErrorMode(ErrorModes uMode);
+
+        /// <summary>
+        /// Enum ErrorModes
+        /// </summary>
+        [Flags]
+        public enum ErrorModes : uint
+        {
+            /// <summary>
+            /// The SYSTE m_ DEFAULT
+            /// </summary>
+            SYSTEM_DEFAULT = 0x0,
+            /// <summary>
+            /// The SE m_ FAILCRITICALERRORS
+            /// </summary>
+            SEM_FAILCRITICALERRORS = 0x0001,
+            /// <summary>
+            /// The SE m_ NOALIGNMENTFAULTEXCEPT
+            /// </summary>
+            SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
+            /// <summary>
+            /// The SE m_ NOGPFAULTERRORBOX
+            /// </summary>
+            SEM_NOGPFAULTERRORBOX = 0x0002,
+            /// <summary>
+            /// The SE m_ NOOPENFILEERRORBOX
+            /// </summary>
+            SEM_NOOPENFILEERRORBOX = 0x8000
+        }
+    }
+}

+ 0 - 0
MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id → MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id


+ 0 - 0
MediaBrowser.Controller/MediaInfo/fonts/fonts.conf → MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf


+ 0 - 0
MediaBrowser.Controller/MediaInfo/readme.txt → MediaBrowser.Server.Implementations/MediaEncoder/readme.txt


+ 0 - 20
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -459,25 +459,5 @@ namespace MediaBrowser.Server.Implementations.Providers
                 _directoryWatchers.RemoveTempIgnore(path);
             }
         }
-
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                _remoteImageCache.Dispose();
-            }
-        }
-
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
-        public void Dispose()
-        {
-            Dispose(true);
-        }
     }
 }

+ 1 - 1
MediaBrowser.Server.Implementations/ServerApplicationPaths.cs

@@ -308,7 +308,7 @@ namespace MediaBrowser.Server.Implementations
             {
                 if (_fFMpegStreamCachePath == null)
                 {
-                    _fFMpegStreamCachePath = Path.Combine(CachePath, "ffmpeg-streams");
+                    _fFMpegStreamCachePath = Path.Combine(CachePath, "encoded-media");
 
                     if (!Directory.Exists(_fFMpegStreamCachePath))
                     {

+ 8 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Implementations;
 using MediaBrowser.Common.Implementations.ScheduledTasks;
 using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
@@ -33,6 +34,7 @@ using MediaBrowser.Server.Implementations.Configuration;
 using MediaBrowser.Server.Implementations.HttpServer;
 using MediaBrowser.Server.Implementations.IO;
 using MediaBrowser.Server.Implementations.Library;
+using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.Providers;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Udp;
@@ -144,6 +146,8 @@ namespace MediaBrowser.ServerApplication
         /// <value>The display preferences manager.</value>
         internal IDisplayPreferencesManager DisplayPreferencesManager { get; set; }
 
+        private IMediaEncoder MediaEncoder { get; set; }
+        
         /// <summary>
         /// The full path to our startmenu shortcut
         /// </summary>
@@ -220,6 +224,9 @@ namespace MediaBrowser.ServerApplication
 
             RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine());
 
+            MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer);
+            RegisterSingleInstance(MediaEncoder);
+
             await ConfigureRepositories().ConfigureAwait(false);
             SetKernelProperties();
             SetStaticProperties();
@@ -230,7 +237,7 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         private void SetKernelProperties()
         {
-            ServerKernel.FFMpegManager = new FFMpegManager(ServerKernel, ZipClient, JsonSerializer, ProtobufSerializer, LogManager, ApplicationPaths);
+            ServerKernel.FFMpegManager = new FFMpegManager(ServerKernel, ApplicationPaths, MediaEncoder);
             ServerKernel.ImageManager = new ImageManager(ServerKernel, ProtobufSerializer, LogManager.GetLogger("ImageManager"), ApplicationPaths);
 
             Parallel.Invoke(

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.67</version>
+        <version>3.0.68</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.67" />
+            <dependency id="MediaBrowser.Common" version="3.0.68" />
             <dependency id="NLog" version="2.0.0.2000" />
             <dependency id="ServiceStack.Text" version="3.9.38" />
             <dependency id="protobuf-net" version="2.0.0.621" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.67</version>
+        <version>3.0.68</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.67</version>
+        <version>3.0.68</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.67" />
+            <dependency id="MediaBrowser.Common" version="3.0.68" />
         </dependencies>
     </metadata>
     <files>