diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 6c12452fc7..a1c32dc636 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/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
/// The iso manager.
protected IIsoManager IsoManager { get; set; }
+ ///
+ /// Gets or sets the media encoder.
+ ///
+ /// The media encoder.
+ protected IMediaEncoder MediaEncoder { get; set; }
+
///
/// Initializes a new instance of the class.
///
@@ -53,12 +61,14 @@ namespace MediaBrowser.Api.Playback
/// The user manager.
/// The library manager.
/// The iso manager.
- protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager)
+ /// The media encoder.
+ protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
{
ApplicationPaths = appPaths;
UserManager = userManager;
LibraryManager = libraryManager;
IsoManager = isoManager;
+ MediaEncoder = mediaEncoder;
}
///
@@ -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);
}
+ ///
+ /// Gets the probe size argument.
+ ///
+ /// The item.
+ /// System.String.
+ protected string GetProbeSizeArgument(BaseItem item)
+ {
+ return MediaEncoder.GetProbeSizeArgument(MediaEncoderHelpers.GetInputType(item));
+ }
+
///
/// Gets the number of audio channels to specify on the command line
///
@@ -477,9 +509,18 @@ namespace MediaBrowser.Api.Playback
/// System.String.
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);
}
///
@@ -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;
}
diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
index a540349f06..b40b7849d4 100644
--- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
+++ b/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
///
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)
{
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index b1879b8d23..08d4468eb9 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/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
///
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,
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 94b9d6924a..6888a1639e 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/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)
{
}
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index a7bcbf821e..50c8210fad 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/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
///
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)
{
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 52052e16e9..c9e8e3a650 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/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
///
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)
{
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 6c9cc8903d..634906afc5 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/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
///
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);
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 59b2ebe410..62b7d5fc94 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/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
/// The video formats.
[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; }
+
+ ///
+ /// Gets or sets the series status.
+ ///
+ /// The series status.
+ [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; }
+
+ ///
+ /// Gets or sets the air days.
+ ///
+ /// The air days.
+ [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; }
}
///
@@ -340,6 +355,23 @@ namespace MediaBrowser.Api.UserLibrary
/// IEnumerable{BaseItem}.
private IEnumerable ApplyAdditionalFilters(GetItems request, IEnumerable items)
{
+ // Filter by Series Status
+ if (!string.IsNullOrEmpty(request.SeriesStatus))
+ {
+ var vals = request.SeriesStatus.Split(',');
+
+ items = items.OfType().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().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
- public class FileSystemRepository : IDisposable
+ public class FileSystemRepository
{
///
/// Contains the list of subfolders under the main directory
@@ -163,24 +163,5 @@ namespace MediaBrowser.Common.IO
return File.Exists(path);
}
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- Dispose(true);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- }
- }
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 22d1e1221d..511c26879a 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -38,6 +38,10 @@
+
+ False
+ ..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll
+
False
..\packages\ServiceStack.Common.3.9.43\lib\net35\ServiceStack.Common.dll
@@ -70,6 +74,8 @@
+
+
diff --git a/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs
new file mode 100644
index 0000000000..56223b99cb
--- /dev/null
+++ b/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.MediaInfo
+{
+ ///
+ /// Interface IMediaEncoder
+ ///
+ public interface IMediaEncoder
+ {
+ ///
+ /// Gets the encoder path.
+ ///
+ /// The encoder path.
+ string EncoderPath { get; }
+
+ ///
+ /// Gets the version.
+ ///
+ /// The version.
+ string Version { get; }
+
+ ///
+ /// Extracts the image.
+ ///
+ /// The input files.
+ /// The type.
+ /// The offset.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ Task ExtractImage(string[] inputFiles, InputType type, TimeSpan? offset, string outputPath, CancellationToken cancellationToken);
+
+ ///
+ /// Extracts the text subtitle.
+ ///
+ /// The input files.
+ /// The type.
+ /// Index of the subtitle stream.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken);
+
+ ///
+ /// Converts the text subtitle to ass.
+ ///
+ /// The input path.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ Task ConvertTextSubtitleToAss(string inputPath, string outputPath, CancellationToken cancellationToken);
+
+ ///
+ /// Gets the media info.
+ ///
+ /// The input files.
+ /// The type.
+ /// The cancellation token.
+ /// Task.
+ Task GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken);
+
+ ///
+ /// Gets the probe size argument.
+ ///
+ /// The type.
+ /// System.String.
+ string GetProbeSizeArgument(InputType type);
+
+ ///
+ /// Gets the input argument.
+ ///
+ /// The input files.
+ /// The type.
+ /// System.String.
+ string GetInputArgument(string[] inputFiles, InputType type);
+ }
+
+ ///
+ /// Enum InputType
+ ///
+ public enum InputType
+ {
+ ///
+ /// The audio file
+ ///
+ AudioFile,
+ ///
+ /// The video file
+ ///
+ VideoFile,
+ ///
+ /// The bluray
+ ///
+ Bluray,
+ ///
+ /// The DVD
+ ///
+ Dvd
+ }
+}
diff --git a/MediaBrowser.Controller/MediaInfo/FFProbeResult.cs b/MediaBrowser.Common/MediaInfo/MediaInfoResult.cs
similarity index 95%
rename from MediaBrowser.Controller/MediaInfo/FFProbeResult.cs
rename to MediaBrowser.Common/MediaInfo/MediaInfoResult.cs
index 06b4ff87d7..98dd36e57c 100644
--- a/MediaBrowser.Controller/MediaInfo/FFProbeResult.cs
+++ b/MediaBrowser.Common/MediaInfo/MediaInfoResult.cs
@@ -2,30 +2,32 @@
using ProtoBuf;
using System.Collections.Generic;
-namespace MediaBrowser.Controller.MediaInfo
+namespace MediaBrowser.Common.MediaInfo
{
///
- /// 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
///
[ProtoContract]
- public class FFProbeResult
+ public class MediaInfoResult
{
///
/// Gets or sets the streams.
///
/// The streams.
[ProtoMember(1)]
- public FFProbeMediaStreamInfo[] streams { get; set; }
+ public MediaStreamInfo[] streams { get; set; }
///
/// Gets or sets the format.
///
/// The format.
[ProtoMember(2)]
- public FFProbeMediaFormatInfo format { get; set; }
+ public MediaFormatInfo format { get; set; }
+ ///
+ /// Gets or sets the chapters.
+ ///
+ /// The chapters.
[ProtoMember(3)]
public List Chapters { get; set; }
}
@@ -34,7 +36,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// Represents a stream within the output
///
[ProtoContract]
- public class FFProbeMediaStreamInfo
+ public class MediaStreamInfo
{
///
/// Gets or sets the index.
@@ -286,7 +288,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// Class MediaFormat
///
[ProtoContract]
- public class FFProbeMediaFormatInfo
+ public class MediaFormatInfo
{
///
/// Gets or sets the filename.
diff --git a/MediaBrowser.Common/packages.config b/MediaBrowser.Common/packages.config
index b5e4f03216..95e6873229 100644
--- a/MediaBrowser.Common/packages.config
+++ b/MediaBrowser.Common/packages.config
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/MediaBrowser.Controller/Drawing/ImageManager.cs b/MediaBrowser.Controller/Drawing/ImageManager.cs
index a5e36da329..b47ae164aa 100644
--- a/MediaBrowser.Controller/Drawing/ImageManager.cs
+++ b/MediaBrowser.Controller/Drawing/ImageManager.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Drawing
///
/// Class ImageManager
///
- public class ImageManager : IDisposable
+ public class ImageManager
{
///
/// Gets the image size cache.
@@ -681,28 +681,5 @@ namespace MediaBrowser.Controller.Drawing
{
return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
}
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- Dispose(true);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected void Dispose(bool dispose)
- {
- if (dispose)
- {
- ImageSizeCache.Dispose();
- ResizedImageCache.Dispose();
- CroppedImageCache.Dispose();
- EnhancedImageCache.Dispose();
- }
- }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 48662fe220..d3a9b0a3fe 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -53,8 +53,7 @@
MinimumRecommendedRules.ruleset
-
- False
+
..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll
@@ -111,6 +110,7 @@
+
@@ -134,7 +134,6 @@
-
@@ -201,15 +200,8 @@
-
-
-
-
-
-
-
if $(ConfigurationName) == Release (
diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
index 9bfebccde8..f262c6c68d 100644
--- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
+++ b/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
///
/// Class FFMpegManager
///
- public class FFMpegManager : IDisposable
+ public class FFMpegManager
{
///
/// Gets or sets the video image cache.
@@ -42,170 +34,35 @@ namespace MediaBrowser.Controller.MediaInfo
/// The subtitle cache.
internal FileSystemRepository SubtitleCache { get; set; }
- ///
- /// Gets or sets the zip client.
- ///
- /// The zip client.
- private readonly IZipClient _zipClient;
-
///
/// The _logger
///
private readonly Kernel _kernel;
- ///
- /// The _logger
- ///
- private readonly ILogger _logger;
-
- ///
- /// Gets the json serializer.
- ///
- /// The json serializer.
- private readonly IJsonSerializer _jsonSerializer;
-
- ///
- /// The _protobuf serializer
- ///
- private readonly IProtobufSerializer _protobufSerializer;
private readonly IServerApplicationPaths _appPaths;
+ private readonly IMediaEncoder _encoder;
///
/// Initializes a new instance of the class.
///
/// The kernel.
- /// The zip client.
- /// The json serializer.
- /// The protobuf serializer.
- /// The logger.
+ /// The app paths.
+ /// The encoder.
/// zipClient
- 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);
- }
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected void Dispose(bool dispose)
- {
- if (dispose)
- {
- SetErrorMode(ErrorModes.SYSTEM_DEFAULT);
-
- AudioImageCache.Dispose();
- VideoImageCache.Dispose();
- }
- }
-
- ///
- /// The FF probe resource pool count
- ///
- private const int FFProbeResourcePoolCount = 3;
- ///
- /// The audio image resource pool count
- ///
- private const int AudioImageResourcePoolCount = 3;
- ///
- /// The video image resource pool count
- ///
- private const int VideoImageResourcePoolCount = 2;
-
- ///
- /// The FF probe resource pool
- ///
- private readonly SemaphoreSlim FFProbeResourcePool = new SemaphoreSlim(FFProbeResourcePoolCount, FFProbeResourcePoolCount);
- ///
- /// The audio image resource pool
- ///
- private readonly SemaphoreSlim AudioImageResourcePool = new SemaphoreSlim(AudioImageResourcePoolCount, AudioImageResourcePoolCount);
- ///
- /// The video image resource pool
- ///
- private readonly SemaphoreSlim VideoImageResourcePool = new SemaphoreSlim(VideoImageResourcePoolCount, VideoImageResourcePoolCount);
-
- ///
- /// Gets or sets the versioned directory path.
- ///
- /// The versioned directory path.
- private string VersionedDirectoryPath { get; set; }
-
- ///
- /// Gets the FFMPEG version.
- ///
- /// The FFMPEG version.
- public string FFMpegVersion
- {
- get { return Path.GetFileNameWithoutExtension(VersionedDirectoryPath); }
- }
-
- ///
- /// The _ FF MPEG path
- ///
- private string _FFMpegPath;
- ///
- /// Gets the path to ffmpeg.exe
- ///
- /// The FF MPEG path.
- public string FFMpegPath
- {
- get
- {
- return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe"));
- }
- }
-
- ///
- /// The _ FF probe path
- ///
- private string _FFProbePath;
- ///
- /// Gets the path to ffprobe.exe
- ///
- /// The FF probe path.
- public string FFProbePath
- {
- get
- {
- return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe"));
- }
}
///
@@ -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
}
}
- ///
- /// The _media tools path
- ///
- private string _mediaToolsPath;
- ///
- /// Gets the folder path to tools
- ///
- /// The media tools path.
- private string MediaToolsPath
- {
- get
- {
- if (_mediaToolsPath == null)
- {
- _mediaToolsPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
-
- if (!Directory.Exists(_mediaToolsPath))
- {
- Directory.CreateDirectory(_mediaToolsPath);
- }
- }
-
- return _mediaToolsPath;
- }
- }
-
- ///
- /// Gets the versioned directory path.
- ///
- /// System.String.
- 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;
- }
-
- ///
- /// Extracts the tools.
- ///
- /// The assembly.
- /// The zip file resource path.
- /// The target path.
- private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
- {
- using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
- {
- _zipClient.ExtractAll(resourceStream, targetPath, false);
- }
-
- ExtractFonts(assembly, targetPath);
- }
-
- ///
- /// Extracts the fonts.
- ///
- /// The assembly.
- /// The target path.
- 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);
- }
-
- ///
- /// Extracts the font config file.
- ///
- /// The assembly.
- /// The fonts directory.
- 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("", "" + fontsDirectory + "");
-
- 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);
- }
- }
- }
- }
- }
-
- ///
- /// Gets the probe size argument.
- ///
- /// The item.
- /// System.String.
- public string GetProbeSizeArgument(BaseItem item)
- {
- var video = item as Video;
-
- return video != null ? GetProbeSizeArgument(video.VideoType, video.IsoType) : string.Empty;
- }
-
- ///
- /// Gets the probe size argument.
- ///
- /// Type of the video.
- /// Type of the iso.
- /// System.String.
- 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;
- }
-
- ///
- /// Runs FFProbe against a BaseItem
- ///
- /// The item.
- /// The input path.
- /// The last date modified.
- /// The cache.
- /// The cancellation token.
- /// Task{FFProbeResult}.
- /// item
- public Task 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(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);
- }
- }
-
- ///
- /// Runs FFProbe against a BaseItem
- ///
- /// The input path.
- /// if set to true [extract chapters].
- /// The cache file.
- /// The probe size argument.
- /// The cancellation token.
- /// Task{FFProbeResult}.
- ///
- private async Task 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 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(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;
- }
-
- ///
- /// Adds the chapters.
- ///
- /// The result.
- /// The standard error.
- private void AddChapters(FFProbeResult result, string standardError)
- {
- var lines = standardError.Split('\n').Select(l => l.TrimStart());
-
- var chapters = new List { };
-
- 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;
- }
-
///
/// The first chapter ticks
///
- private static long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
+ private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
///
/// 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
}
}
- ///
- /// Extracts an image from an Audio file and returns a Task whose result indicates whether it was successful or not
- ///
- /// The input path.
- /// The output path.
- /// The cancellation token.
- /// Task{System.Boolean}.
- /// input
- public async Task 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;
- }
-
- ///
- /// Determines whether [is subtitle cached] [the specified input].
- ///
- /// The input.
- /// Index of the subtitle stream.
- /// The output extension.
- /// true if [is subtitle cached] [the specified input]; otherwise, false.
- public bool IsSubtitleCached(Video input, int subtitleStreamIndex, string outputExtension)
- {
- return SubtitleCache.ContainsFilePath(GetSubtitleCachePath(input, subtitleStreamIndex, outputExtension));
- }
-
///
/// Gets the subtitle cache path.
///
@@ -811,364 +231,5 @@ namespace MediaBrowser.Controller.MediaInfo
{
return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks, outputExtension);
}
-
- ///
- /// Extracts the text subtitle.
- ///
- /// The input.
- /// Index of the subtitle stream.
- /// The output path.
- /// The cancellation token.
- /// Task{System.Boolean}.
- /// input
- public async Task 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;
- }
-
- ///
- /// Converts the text subtitle.
- ///
- /// The media stream.
- /// The output path.
- /// The cancellation token.
- /// Task{System.Boolean}.
- /// mediaStream
- /// The given MediaStream is not an external subtitle stream
- public async Task 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;
- }
-
- ///
- /// Extracts an image from a Video and returns a Task whose result indicates whether it was successful or not
- ///
- /// The input path.
- /// The offset.
- /// The output path.
- /// The cancellation token.
- /// Task{System.Boolean}.
- /// video
- public async Task 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;
- }
-
- ///
- /// Gets the input argument.
- ///
- /// The item.
- /// System.String.
- 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);
- }
-
- ///
- /// Gets the file input argument.
- ///
- /// The path.
- /// System.String.
- private string GetFileInputArgument(string path)
- {
- return string.Format("file:\"{0}\"", path);
- }
-
- ///
- /// Gets the input argument.
- ///
- /// The item.
- /// The mount.
- /// System.String.
- 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);
- }
-
- ///
- /// Gets the bluray input argument.
- ///
- /// The bluray root.
- /// System.String.
- public string GetBlurayInputArgument(string blurayRoot)
- {
- return string.Format("bluray:\"{0}\"", blurayRoot);
- }
-
- ///
- /// Gets the DVD input argument.
- ///
- /// The playable stream files.
- /// System.String.
- public string GetDvdInputArgument(IEnumerable 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]);
- }
-
- ///
- /// Processes the exited.
- ///
- /// The sender.
- /// The instance containing the event data.
- void ProcessExited(object sender, EventArgs e)
- {
- ((Process)sender).Dispose();
- }
-
- ///
- /// Provides a non-blocking method to start a process and wait asynchronously for it to exit
- ///
- /// The process.
- /// Task{System.Boolean}.
- private static Task RunAsync(Process process)
- {
- var tcs = new TaskCompletionSource();
-
- process.EnableRaisingEvents = true;
- process.Exited += (sender, args) => tcs.SetResult(true);
-
- process.Start();
-
- return tcs.Task;
- }
-
- ///
- /// Sets the error mode.
- ///
- /// The u mode.
- /// ErrorModes.
- [DllImport("kernel32.dll")]
- static extern ErrorModes SetErrorMode(ErrorModes uMode);
-
- ///
- /// Enum ErrorModes
- ///
- [Flags]
- public enum ErrorModes : uint
- {
- ///
- /// The SYSTE m_ DEFAULT
- ///
- SYSTEM_DEFAULT = 0x0,
- ///
- /// The SE m_ FAILCRITICALERRORS
- ///
- SEM_FAILCRITICALERRORS = 0x0001,
- ///
- /// The SE m_ NOALIGNMENTFAULTEXCEPT
- ///
- SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
- ///
- /// The SE m_ NOGPFAULTERRORBOX
- ///
- SEM_NOGPFAULTERRORBOX = 0x0002,
- ///
- /// The SE m_ NOOPENFILEERRORBOX
- ///
- SEM_NOOPENFILEERRORBOX = 0x8000
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 902ed21bcd..cb7237a9df 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/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
{
///
/// Downloads the and save image.
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs
index 6edd28b245..9fa6363a83 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs
+++ b/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 : 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;
}
///
@@ -53,7 +57,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
{
get
{
- return Kernel.Instance.FFMpegManager.FFMpegVersion;
+ return MediaEncoder.Version;
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs
index 7c977d02f7..eac10d5221 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs
+++ b/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
///
///
- public abstract class BaseFFProbeProvider : BaseFFMpegProvider, IDisposable
+ public abstract class BaseFFProbeProvider : BaseFFMpegProvider
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;
+
///
/// Gets or sets the FF probe cache.
///
@@ -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;
}
+ ///
+ /// Gets the media info.
+ ///
+ /// The item.
+ /// The iso mount.
+ /// The last date modified.
+ /// The cache.
+ /// The cancellation token.
+ /// Task{MediaInfoResult}.
+ /// inputPath
+ /// or
+ /// cache
+ private async Task 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(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;
+ }
+
///
/// Gets a value indicating whether [refresh on version change].
///
@@ -147,7 +203,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
/// Normalizes the FF probe result.
///
/// The result.
- 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
/// The result.
/// The iso mount.
/// Task.
- protected abstract void Fetch(T item, CancellationToken cancellationToken, FFProbeResult result, IIsoMount isoMount);
+ protected abstract void Fetch(T item, CancellationToken cancellationToken, MediaInfoResult result, IIsoMount isoMount);
///
/// Converts ffprobe stream info to our MediaStream class
@@ -188,7 +244,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
/// The stream info.
/// The format info.
/// MediaStream.
- 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(dict, StringComparer.OrdinalIgnoreCase);
}
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- FFProbeCache.Dispose();
- }
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs
index 68e552d3c4..421b0522da 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs
+++ b/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
///
public class FFMpegAudioImageProvider : BaseFFMpegProvider
public class FFProbeAudioInfoProvider : BaseFFProbeProvider
public class FFProbeVideoInfoProvider : BaseFFProbeProvider
private readonly IIsoManager _isoManager;
- ///
- /// The _protobuf serializer
- ///
- private readonly IProtobufSerializer _protobufSerializer;
-
///
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
///
@@ -187,7 +181,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
/// The data.
/// The iso mount.
/// Task.
- 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(cacheFile);
+ result = ProtobufSerializer.DeserializeFromFile(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);
}
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected override void Dispose(bool dispose)
- {
- if (dispose)
- {
- BdInfoCache.Dispose();
- }
-
- base.Dispose(dispose);
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs
new file mode 100644
index 0000000000..14372553b4
--- /dev/null
+++ b/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
+{
+ ///
+ /// Class MediaEncoderHelpers
+ ///
+ public static class MediaEncoderHelpers
+ {
+ ///
+ /// Gets the input argument.
+ ///
+ /// The video.
+ /// The iso mount.
+ /// The type.
+ /// System.String[][].
+ 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;
+ }
+
+ ///
+ /// Gets the type of the input.
+ ///
+ /// The item.
+ /// InputType.
+ 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;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs
index 37024795ca..3a1d14065e 100644
--- a/MediaBrowser.Model/Querying/ItemQuery.cs
+++ b/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
/// The image types.
public ImageType[] ImageTypes { get; set; }
+ ///
+ /// Gets or sets the air days.
+ ///
+ /// The air days.
+ public DayOfWeek[] AirDays { get; set; }
+
+ ///
+ /// Gets or sets the series status.
+ ///
+ /// The series status.
+ public SeriesStatus[] SeriesStatuses { get; set; }
+
///
/// Gets or sets the ids, which are specific items to retrieve
///
diff --git a/MediaBrowser.Model/packages.config b/MediaBrowser.Model/packages.config
index 4ffd05bc07..b79466fc47 100644
--- a/MediaBrowser.Model/packages.config
+++ b/MediaBrowser.Model/packages.config
@@ -1,5 +1,4 @@
-
\ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index f2593f1c5a..81d2bbca4b 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -147,6 +147,7 @@
+
@@ -202,6 +203,7 @@
+
PreserveNewest
@@ -253,6 +255,9 @@
+
+
+
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
new file mode 100644
index 0000000000..fce88e3d8b
--- /dev/null
+++ b/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
+{
+ ///
+ /// Class MediaEncoder
+ ///
+ public class MediaEncoder : IMediaEncoder, IDisposable
+ {
+ ///
+ /// Gets or sets the zip client.
+ ///
+ /// The zip client.
+ private readonly IZipClient _zipClient;
+
+ ///
+ /// The _logger
+ ///
+ private readonly ILogger _logger;
+
+ ///
+ /// The _app paths
+ ///
+ private readonly IApplicationPaths _appPaths;
+
+ ///
+ /// Gets the json serializer.
+ ///
+ /// The json serializer.
+ private readonly IJsonSerializer _jsonSerializer;
+
+ ///
+ /// The video image resource pool
+ ///
+ private readonly SemaphoreSlim _videoImageResourcePool = new SemaphoreSlim(2, 2);
+
+ ///
+ /// The audio image resource pool
+ ///
+ private readonly SemaphoreSlim _audioImageResourcePool = new SemaphoreSlim(3, 3);
+
+ ///
+ /// The _subtitle extraction resource pool
+ ///
+ private readonly SemaphoreSlim _subtitleExtractionResourcePool = new SemaphoreSlim(2, 2);
+
+ ///
+ /// The FF probe resource pool
+ ///
+ private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(3, 3);
+
+ ///
+ /// Gets or sets the versioned directory path.
+ ///
+ /// The versioned directory path.
+ private string VersionedDirectoryPath { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ /// The zip client.
+ /// The app paths.
+ /// The json serializer.
+ 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());
+ }
+
+ ///
+ /// The _media tools path
+ ///
+ private string _mediaToolsPath;
+ ///
+ /// Gets the folder path to tools
+ ///
+ /// The media tools path.
+ private string MediaToolsPath
+ {
+ get
+ {
+ if (_mediaToolsPath == null)
+ {
+ _mediaToolsPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
+
+ if (!Directory.Exists(_mediaToolsPath))
+ {
+ Directory.CreateDirectory(_mediaToolsPath);
+ }
+ }
+
+ return _mediaToolsPath;
+ }
+ }
+
+ ///
+ /// Gets the encoder path.
+ ///
+ /// The encoder path.
+ public string EncoderPath
+ {
+ get { return FFMpegPath; }
+ }
+
+ ///
+ /// The _ FF MPEG path
+ ///
+ private string _FFMpegPath;
+ ///
+ /// Gets the path to ffmpeg.exe
+ ///
+ /// The FF MPEG path.
+ public string FFMpegPath
+ {
+ get
+ {
+ return _FFMpegPath ?? (_FFMpegPath = Path.Combine(VersionedDirectoryPath, "ffmpeg.exe"));
+ }
+ }
+
+ ///
+ /// The _ FF probe path
+ ///
+ private string _FFProbePath;
+ ///
+ /// Gets the path to ffprobe.exe
+ ///
+ /// The FF probe path.
+ private string FFProbePath
+ {
+ get
+ {
+ return _FFProbePath ?? (_FFProbePath = Path.Combine(VersionedDirectoryPath, "ffprobe.exe"));
+ }
+ }
+
+ ///
+ /// Gets the version.
+ ///
+ /// The version.
+ public string Version
+ {
+ get { return Path.GetFileNameWithoutExtension(VersionedDirectoryPath); }
+ }
+
+ ///
+ /// Gets the versioned directory path.
+ ///
+ /// System.String.
+ 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;
+ }
+
+ ///
+ /// Extracts the tools.
+ ///
+ /// The assembly.
+ /// The zip file resource path.
+ /// The target path.
+ private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
+ {
+ using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
+ {
+ _zipClient.ExtractAll(resourceStream, targetPath, false);
+ }
+
+ ExtractFonts(assembly, targetPath);
+ }
+
+ ///
+ /// Extracts the fonts.
+ ///
+ /// The assembly.
+ /// The target path.
+ 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);
+ }
+
+ ///
+ /// Extracts the font config file.
+ ///
+ /// The assembly.
+ /// The fonts directory.
+ /// Task.
+ 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("", "" + fontsDirectory + "");
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets the media info.
+ ///
+ /// The input files.
+ /// The type.
+ /// The cancellation token.
+ /// Task.
+ public Task GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken)
+ {
+ return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.AudioFile, GetProbeSizeArgument(type), cancellationToken);
+ }
+
+ ///
+ /// Gets the input argument.
+ ///
+ /// The input files.
+ /// The type.
+ /// System.String.
+ /// Unrecognized InputType
+ 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;
+ }
+
+ ///
+ /// Gets the probe size argument.
+ ///
+ /// The type.
+ /// System.String.
+ public string GetProbeSizeArgument(InputType type)
+ {
+ return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ }
+
+ ///
+ /// Gets the media info internal.
+ ///
+ /// The input path.
+ /// if set to true [extract chapters].
+ /// The probe size argument.
+ /// The cancellation token.
+ /// Task{MediaInfoResult}.
+ ///
+ private async Task 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 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(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;
+ }
+
+ ///
+ /// Adds the chapters.
+ ///
+ /// The result.
+ /// The standard error.
+ private void AddChapters(MediaInfoResult result, string standardError)
+ {
+ var lines = standardError.Split('\n').Select(l => l.TrimStart());
+
+ var chapters = new List { };
+
+ 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;
+ }
+
+ ///
+ /// Processes the exited.
+ ///
+ /// The sender.
+ /// The instance containing the event data.
+ void ProcessExited(object sender, EventArgs e)
+ {
+ ((Process)sender).Dispose();
+ }
+
+ ///
+ /// Converts the text subtitle to ass.
+ ///
+ /// The input path.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ /// inputPath
+ /// or
+ /// outputPath
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Extracts the text subtitle.
+ ///
+ /// The input files.
+ /// The type.
+ /// Index of the subtitle stream.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ /// Must use inputPath list overload
+ public Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
+ {
+ return ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, outputPath, cancellationToken);
+ }
+
+ ///
+ /// Extracts the text subtitle.
+ ///
+ /// The input path.
+ /// Index of the subtitle stream.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ /// inputPath
+ /// or
+ /// outputPath
+ /// or
+ /// cancellationToken
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Extracts the image.
+ ///
+ /// The input files.
+ /// The type.
+ /// The offset.
+ /// The output path.
+ /// The cancellation token.
+ /// Task.
+ /// Must use inputPath list overload
+ 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);
+ }
+
+ ///
+ /// Extracts the image.
+ ///
+ /// The input path.
+ /// The offset.
+ /// The output path.
+ /// The resource pool.
+ /// The cancellation token.
+ /// Task.
+ /// inputPath
+ /// or
+ /// outputPath
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Starts the and wait for process.
+ ///
+ /// The process.
+ /// true if XXXX, false otherwise
+ 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;
+ }
+
+ ///
+ /// Gets the file input argument.
+ ///
+ /// The path.
+ /// System.String.
+ public string GetFileInputArgument(string path)
+ {
+ return string.Format("file:\"{0}\"", path);
+ }
+
+ ///
+ /// Gets the concat input argument.
+ ///
+ /// The playable stream files.
+ /// System.String.
+ 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]);
+ }
+
+ ///
+ /// Gets the bluray input argument.
+ ///
+ /// The bluray root.
+ /// System.String.
+ public string GetBlurayInputArgument(string blurayRoot)
+ {
+ return string.Format("bluray:\"{0}\"", blurayRoot);
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ _videoImageResourcePool.Dispose();
+ }
+
+ SetErrorMode(ErrorModes.SYSTEM_DEFAULT);
+ }
+
+ ///
+ /// Sets the error mode.
+ ///
+ /// The u mode.
+ /// ErrorModes.
+ [DllImport("kernel32.dll")]
+ static extern ErrorModes SetErrorMode(ErrorModes uMode);
+
+ ///
+ /// Enum ErrorModes
+ ///
+ [Flags]
+ public enum ErrorModes : uint
+ {
+ ///
+ /// The SYSTE m_ DEFAULT
+ ///
+ SYSTEM_DEFAULT = 0x0,
+ ///
+ /// The SE m_ FAILCRITICALERRORS
+ ///
+ SEM_FAILCRITICALERRORS = 0x0001,
+ ///
+ /// The SE m_ NOALIGNMENTFAULTEXCEPT
+ ///
+ SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
+ ///
+ /// The SE m_ NOGPFAULTERRORBOX
+ ///
+ SEM_NOGPFAULTERRORBOX = 0x0002,
+ ///
+ /// The SE m_ NOOPENFILEERRORBOX
+ ///
+ SEM_NOOPENFILEERRORBOX = 0x8000
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id b/MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id
similarity index 100%
rename from MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id
rename to MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id
diff --git a/MediaBrowser.Controller/MediaInfo/fonts/fonts.conf b/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
similarity index 100%
rename from MediaBrowser.Controller/MediaInfo/fonts/fonts.conf
rename to MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
diff --git a/MediaBrowser.Controller/MediaInfo/readme.txt b/MediaBrowser.Server.Implementations/MediaEncoder/readme.txt
similarity index 100%
rename from MediaBrowser.Controller/MediaInfo/readme.txt
rename to MediaBrowser.Server.Implementations/MediaEncoder/readme.txt
diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
index c34f8a1baf..b08f2eb513 100644
--- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
@@ -459,25 +459,5 @@ namespace MediaBrowser.Server.Implementations.Providers
_directoryWatchers.RemoveTempIgnore(path);
}
}
-
- ///
- /// Releases unmanaged and - optionally - managed resources.
- ///
- /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- _remoteImageCache.Dispose();
- }
- }
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- Dispose(true);
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
index 0863e592ba..25d9cf5330 100644
--- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
+++ b/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))
{
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 90de11976c..9bac7b9f24 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/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
/// The display preferences manager.
internal IDisplayPreferencesManager DisplayPreferencesManager { get; set; }
+ private IMediaEncoder MediaEncoder { get; set; }
+
///
/// The full path to our startmenu shortcut
///
@@ -220,6 +224,9 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance(() => 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
///
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(
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index b89c8f9f2f..5fd06a8a3f 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common.Internal
- 3.0.67
+ 3.0.68
MediaBrowser.Common.Internal
Luke
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.
Copyright © Media Browser 2013
-
+
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index f628fee30d..1242087654 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common
- 3.0.67
+ 3.0.68
MediaBrowser.Common
Media Browser Team
ebr,Luke,scottisafool
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 6058d6e04c..4eb5002c93 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Server.Core
- 3.0.67
+ 3.0.68
Media Browser.Server.Core
Media Browser Team
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains core components required to build plugins for Media Browser Server.
Copyright © Media Browser 2013
-
+