瀏覽代碼

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

Luke Pulverenti 12 年之前
父節點
當前提交
cb39f8e7b5
共有 37 個文件被更改,包括 1430 次插入1179 次删除
  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>