aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2013-04-07 16:55:05 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2013-04-07 16:55:05 -0400
commitcb39f8e7b5f71dcf0de2681d953b016e29adbc54 (patch)
treefd2ef9a2a8b284a8810ece7b6ead4df0da3d133d
parentd14c3b31ff5665b03fee07aab46532fcf3069c1a (diff)
extracted a media encoding interface to keep ffmpeg out of nuget packages
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs63
-rw-r--r--MediaBrowser.Api/Playback/Hls/AudioHlsService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs12
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs7
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs33
-rw-r--r--MediaBrowser.Common/IO/FileSystemRepository.cs21
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj6
-rw-r--r--MediaBrowser.Common/MediaInfo/IMediaEncoder.cs102
-rw-r--r--MediaBrowser.Common/MediaInfo/MediaInfoResult.cs (renamed from MediaBrowser.Controller/MediaInfo/FFProbeResult.cs)20
-rw-r--r--MediaBrowser.Common/packages.config1
-rw-r--r--MediaBrowser.Controller/Drawing/ImageManager.cs25
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj12
-rw-r--r--MediaBrowser.Controller/MediaInfo/FFMpegManager.cs977
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs7
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs8
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs99
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs18
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs45
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs11
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs32
-rw-r--r--MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs96
-rw-r--r--MediaBrowser.Model/Querying/ItemQuery.cs15
-rw-r--r--MediaBrowser.Model/packages.config1
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj5
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs930
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id (renamed from MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id)0
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf (renamed from MediaBrowser.Controller/MediaInfo/fonts/fonts.conf)0
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/readme.txt (renamed from MediaBrowser.Controller/MediaInfo/readme.txt)0
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ProviderManager.cs20
-rw-r--r--MediaBrowser.Server.Implementations/ServerApplicationPaths.cs2
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs9
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
37 files changed, 1430 insertions, 1179 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 6c12452fc..a1c32dc63 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;
@@ -47,18 +49,26 @@ namespace MediaBrowser.Api.Playback
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>
/// <param name="appPaths">The app paths.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="isoManager">The iso manager.</param>
- 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;
}
@@ -366,6 +388,16 @@ namespace MediaBrowser.Api.Playback
}
/// <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>
/// <param name="request">The request.</param>
@@ -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;
}
diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
index a540349f0..b40b7849d 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
/// </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)
{
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index b1879b8d2..08d4468eb 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
/// </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,
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 94b9d6924..6888a1639 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 a7bcbf821..50c8210fa 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
/// </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)
{
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 52052e16e..c9e8e3a65 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
/// </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)
{
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 6c9cc8903..634906afc 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
/// </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);
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 59b2ebe41..62b7d5fc9 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
/// <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(',');
diff --git a/MediaBrowser.Common/IO/FileSystemRepository.cs b/MediaBrowser.Common/IO/FileSystemRepository.cs
index 3a4987a94..f2fa8ce54 100644
--- a/MediaBrowser.Common/IO/FileSystemRepository.cs
+++ b/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)
- {
- }
- }
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 22d1e1221..511c26879 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/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" />
diff --git a/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs b/MediaBrowser.Common/MediaInfo/IMediaEncoder.cs
new file mode 100644
index 000000000..56223b99c
--- /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
+{
+ /// <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
+ }
+}
diff --git a/MediaBrowser.Controller/MediaInfo/FFProbeResult.cs b/MediaBrowser.Common/MediaInfo/MediaInfoResult.cs
index 06b4ff87d..98dd36e57 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
{
/// <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.
diff --git a/MediaBrowser.Common/packages.config b/MediaBrowser.Common/packages.config
index b5e4f0321..95e687322 100644
--- a/MediaBrowser.Common/packages.config
+++ b/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> \ No newline at end of file
diff --git a/MediaBrowser.Controller/Drawing/ImageManager.cs b/MediaBrowser.Controller/Drawing/ImageManager.cs
index a5e36da32..b47ae164a 100644
--- a/MediaBrowser.Controller/Drawing/ImageManager.cs
+++ b/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();
- }
- }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 48662fe22..d3a9b0a3f 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/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 (
diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
index 9bfebccde..f262c6c68 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
/// <summary>
/// Class FFMpegManager
/// </summary>
- public class FFMpegManager : IDisposable
+ public class FFMpegManager
{
/// <summary>
/// Gets or sets the video image cache.
@@ -43,169 +35,34 @@ namespace MediaBrowser.Controller.MediaInfo
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))
{
@@ -287,385 +144,9 @@ 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;
}
@@ -737,70 +221,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>
/// <param name="input">The input.</param>
@@ -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
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 902ed21bc..cb7237a9d 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
{
/// <summary>
/// Downloads the and save image.
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFMpegProvider.cs
index 6edd28b24..9fa6363a8 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<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;
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs
index 7c977d02f..eac10d522 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
/// </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();
@@ -111,6 +112,61 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
}
/// <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>
/// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
@@ -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);
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegAudioImageProvider.cs
index 68e552d3c..421b0522d 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
/// </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
{
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs
index bc851a0cb..a29dbb043 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs
+++ b/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
{
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs
index 8390a0cee..e767d5196 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs
+++ b/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)
{
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs
index a2a9fa0d1..07b71576b 100644
--- a/MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs
+++ b/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"));
}
@@ -62,11 +61,6 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
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>
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
@@ -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);
- }
}
}
diff --git a/MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/Providers/MediaInfo/MediaEncoderHelpers.cs
new file mode 100644
index 000000000..14372553b
--- /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
+{
+ /// <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;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs
index 37024795c..3a1d14065 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
@@ -142,6 +141,18 @@ namespace MediaBrowser.Model.Querying
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>
/// <value>The ids.</value>
diff --git a/MediaBrowser.Model/packages.config b/MediaBrowser.Model/packages.config
index 4ffd05bc0..b79466fc4 100644
--- a/MediaBrowser.Model/packages.config
+++ b/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> \ 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 f2593f1c5..81d2bbca4 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/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 />
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
new file mode 100644
index 000000000..fce88e3d8
--- /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
+{
+ /// <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
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id b/MediaBrowser.Server.Implementations/MediaEncoder/ffmpeg20130405.zip.REMOVED.git-id
index 363d8552f..363d8552f 100644
--- a/MediaBrowser.Controller/MediaInfo/ffmpeg20130405.zip.REMOVED.git-id
+++ b/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
index 648bdb7b2..648bdb7b2 100644
--- a/MediaBrowser.Controller/MediaInfo/fonts/fonts.conf
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
diff --git a/MediaBrowser.Controller/MediaInfo/readme.txt b/MediaBrowser.Server.Implementations/MediaEncoder/readme.txt
index b32dd9aec..b32dd9aec 100644
--- a/MediaBrowser.Controller/MediaInfo/readme.txt
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/readme.txt
diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
index c34f8a1ba..b08f2eb51 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);
}
}
-
- /// <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);
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
index 0863e592b..25d9cf533 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 90de11976..9bac7b9f2 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
/// <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(
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index b89c8f9f2..5fd06a8a3 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/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" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index f628fee30..124208765 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/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>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 6058d6e04..4eb5002c9 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/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>