aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2016-12-18 00:44:33 -0500
committerGitHub <noreply@github.com>2016-12-18 00:44:33 -0500
commite7cebb91a73354dc3e0d0b6340c9fbd6511f4406 (patch)
tree6f1c368c766c17b7514fe749c0e92e69cd89194a /MediaBrowser.MediaEncoding
parent025905a3e4d50b9a2e07fbf4ff0a203af6604ced (diff)
parentaaa027f3229073e9a40756c3157d41af2a442922 (diff)
Merge pull request #2350 from MediaBrowser/beta
Beta
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs13
-rw-r--r--MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs44
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs54
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs14
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs317
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs5
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj47
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets6
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs173
-rw-r--r--MediaBrowser.MediaEncoding/Probing/whitelist.txt1
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs29
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs349
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs190
-rw-r--r--MediaBrowser.MediaEncoding/packages.config4
-rw-r--r--MediaBrowser.MediaEncoding/project.json17
19 files changed, 856 insertions, 426 deletions
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index e435c00f2..bf7343f3d 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -4,6 +4,8 @@ using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Text;
namespace MediaBrowser.MediaEncoding.BdInfo
{
@@ -12,6 +14,15 @@ namespace MediaBrowser.MediaEncoding.BdInfo
/// </summary>
public class BdInfoExaminer : IBlurayExaminer
{
+ private readonly IFileSystem _fileSystem;
+ private readonly ITextEncoding _textEncoding;
+
+ public BdInfoExaminer(IFileSystem fileSystem, ITextEncoding textEncoding)
+ {
+ _fileSystem = fileSystem;
+ _textEncoding = textEncoding;
+ }
+
/// <summary>
/// Gets the disc info.
/// </summary>
@@ -19,7 +30,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
/// <returns>BlurayDiscInfo.</returns>
public BlurayDiscInfo GetDiscInfo(string path)
{
- var bdrom = new BDROM(path);
+ var bdrom = new BDROM(path, _fileSystem, _textEncoding);
bdrom.Scan();
diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
index 42a28d313..5beab746d 100644
--- a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
+++ b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
@@ -2,7 +2,9 @@
using MediaBrowser.Model.Configuration;
using System.Collections.Generic;
using System.IO;
-using CommonIO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
namespace MediaBrowser.MediaEncoding.Configuration
{
@@ -48,7 +50,7 @@ namespace MediaBrowser.MediaEncoding.Configuration
// Validate
if (!_fileSystem.DirectoryExists(newPath))
{
- throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+ throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
index f42582270..5a554d26f 100644
--- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -7,13 +7,13 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Model.Diagnostics;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class AudioEncoder : BaseEncoder
{
- public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
+ public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory)
{
}
@@ -114,5 +114,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
get { return false; }
}
+
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index c7b78aae3..b8087fded 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -11,13 +11,12 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -33,6 +32,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly ISessionManager SessionManager;
protected readonly ISubtitleEncoder SubtitleEncoder;
protected readonly IMediaSourceManager MediaSourceManager;
+ protected IProcessFactory ProcessFactory;
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -44,7 +44,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
ILibraryManager libraryManager,
ISessionManager sessionManager,
ISubtitleEncoder subtitleEncoder,
- IMediaSourceManager mediaSourceManager)
+ IMediaSourceManager mediaSourceManager, IProcessFactory processFactory)
{
MediaEncoder = mediaEncoder;
Logger = logger;
@@ -55,6 +55,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
SessionManager = sessionManager;
SubtitleEncoder = subtitleEncoder;
MediaSourceManager = mediaSourceManager;
+ ProcessFactory = processFactory;
}
public async Task<EncodingJob> Start(EncodingJobOptions options,
@@ -73,27 +74,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false);
- var process = new Process
+ var process = ProcessFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both stdout and stderr or deadlocks may occur
- //RedirectStandardOutput = true,
- RedirectStandardError = true,
- RedirectStandardInput = true,
+ CreateNoWindow = true,
+ UseShellExecute = false,
- FileName = MediaEncoder.EncoderPath,
- Arguments = commandLineArgs,
+ // Must consume both stdout and stderr or deadlocks may occur
+ //RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- },
+ FileName = MediaEncoder.EncoderPath,
+ Arguments = commandLineArgs,
+ IsHidden = true,
+ ErrorDialog = false,
EnableRaisingEvents = true
- };
+ });
var workingDirectory = GetWorkingDirectory(options);
if (!string.IsNullOrWhiteSpace(workingDirectory))
@@ -110,7 +107,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
+ encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false);
@@ -147,7 +144,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return encodingJob;
}
- private void Cancel(Process process, EncodingJob job)
+ private void Cancel(IProcess process, EncodingJob job)
{
Logger.Info("Killing ffmpeg process for {0}", job.OutputFilePath);
@@ -162,7 +159,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="process">The process.</param>
/// <param name="job">The job.</param>
- private void OnFfMpegProcessExited(Process process, EncodingJob job)
+ private void OnFfMpegProcessExited(IProcess process, EncodingJob job)
{
job.HasExited = true;
@@ -215,7 +212,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
try
{
- job.TaskCompletionSource.TrySetException(new ApplicationException("Encoding failed"));
+ job.TaskCompletionSource.TrySetException(new Exception("Encoding failed"));
}
catch
{
@@ -773,7 +770,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 7acff8fc8..b37e783b8 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
+using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -8,10 +10,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class EncoderValidator
{
private readonly ILogger _logger;
+ private readonly IProcessFactory _processFactory;
- public EncoderValidator(ILogger logger)
+ public EncoderValidator(ILogger logger, IProcessFactory processFactory)
{
_logger = logger;
+ _processFactory = processFactory;
}
public Tuple<List<string>, List<string>> Validate(string encoderPath)
@@ -26,15 +30,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
return new Tuple<List<string>, List<string>>(decoders, encoders);
}
- public bool ValidateVersion(string encoderAppPath)
+ public bool ValidateVersion(string encoderAppPath, bool logOutput)
{
string output = string.Empty;
try
{
output = GetProcessOutput(encoderAppPath, "-version");
}
- catch
+ catch (Exception ex)
{
+ if (logOutput)
+ {
+ _logger.ErrorException("Error validating encoder", ex);
+ }
}
if (string.IsNullOrWhiteSpace(output))
@@ -42,11 +50,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
+ if (logOutput)
+ {
+ _logger.Info("ffmpeg info: {0}", output);
+ }
+
if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
+ output = " " + output + " ";
+
+ for (var i = 2013; i <= 2015; i++)
+ {
+ var yearString = i.ToString(CultureInfo.InvariantCulture);
+ if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return false;
+ }
+ }
+
return true;
}
@@ -57,8 +81,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
output = GetProcessOutput(encoderAppPath, "-decoders");
}
- catch
+ catch (Exception )
{
+ //_logger.ErrorException("Error detecting available decoders", ex);
}
var found = new List<string>();
@@ -140,19 +165,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
private string GetProcessOutput(string path, string arguments)
{
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = path,
- Arguments = arguments,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false,
- RedirectStandardOutput = true
- }
- };
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = path,
+ Arguments = arguments,
+ IsHidden = true,
+ ErrorDialog = false,
+ RedirectStandardOutput = true
+ });
_logger.Info("Running {0} {1}", path, arguments);
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
index 0c7ff1b76..b5ff5cbb6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index f811a8d48..e197bdb6f 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -606,9 +606,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
// vaapi will throw an error with this input
// [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
- if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase) && videoStream.Level == -99)
+ if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
- return false;
+ if (videoStream.Level == -99 || videoStream.Level == 15)
+ {
+ return false;
+ }
}
}
return true;
diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
index 71306e0ec..42048ab9e 100644
--- a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
@@ -3,9 +3,11 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Model.IO;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
@@ -86,9 +88,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>Task.</returns>
private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
{
- var existingFile = Directory
- .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
- .FirstOrDefault();
+ var existingFile = _fileSystem
+ .GetFilePaths(_appPaths.ProgramDataPath, true)
+ .FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase));
if (existingFile != null)
{
@@ -168,8 +170,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
var bytes = Encoding.UTF8.GetBytes(contents);
- using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
- FileShare.Read, true))
+ using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write,
+ FileShareMode.Read, true))
{
await fileStream.WriteAsync(bytes, 0, bytes.Length);
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 340990373..23e63dad0 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -14,18 +14,17 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Diagnostics;
+using MediaBrowser.Model.System;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -80,14 +79,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly Func<IMediaSourceManager> MediaSourceManager;
private readonly IHttpClient _httpClient;
private readonly IZipClient _zipClient;
- private readonly IMemoryStreamProvider _memoryStreamProvider;
+ private readonly IProcessFactory _processFactory;
+ private readonly IMemoryStreamFactory _memoryStreamProvider;
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
private readonly bool _hasExternalEncoder;
- private string _originalFFMpegPath;
- private string _originalFFProbePath;
+ private readonly string _originalFFMpegPath;
+ private readonly string _originalFFProbePath;
+ private readonly int DefaultImageExtractionTimeoutMs;
+ private readonly bool EnableEncoderFontFile;
- public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamProvider memoryStreamProvider)
+ private readonly IEnvironmentInfo _environmentInfo;
+
+ public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory,
+ int defaultImageExtractionTimeoutMs,
+ bool enableEncoderFontFile, IEnvironmentInfo environmentInfo)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -103,18 +109,80 @@ namespace MediaBrowser.MediaEncoding.Encoder
_httpClient = httpClient;
_zipClient = zipClient;
_memoryStreamProvider = memoryStreamProvider;
+ _processFactory = processFactory;
+ DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
+ EnableEncoderFontFile = enableEncoderFontFile;
+ _environmentInfo = environmentInfo;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
_originalFFProbePath = ffProbePath;
_originalFFMpegPath = ffMpegPath;
_hasExternalEncoder = hasExternalEncoder;
+
+ SetEnvironmentVariable();
+ }
+
+ private readonly object _logLock = new object();
+ public void SetLogFilename(string name)
+ {
+ lock (_logLock)
+ {
+ try
+ {
+ _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32");
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error setting FFREPORT environment variable", ex);
+ }
+ }
+ }
+
+ public void ClearLogFilename()
+ {
+ lock (_logLock)
+ {
+ try
+ {
+ _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null);
+ }
+ catch (Exception ex)
+ {
+ //_logger.ErrorException("Error setting FFREPORT environment variable", ex);
+ }
+ }
+ }
+
+ private void SetEnvironmentVariable()
+ {
+ try
+ {
+ //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32");
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error setting FFREPORT environment variable", ex);
+ }
+ try
+ {
+ //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32");
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error setting FFREPORT environment variable", ex);
+ }
}
public string EncoderLocationType
{
get
{
+ if (_hasExternalEncoder)
+ {
+ return "External";
+ }
+
if (string.IsNullOrWhiteSpace(FFMpegPath))
{
return null;
@@ -157,12 +225,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(FFMpegPath))
{
- var result = new EncoderValidator(_logger).Validate(FFMpegPath);
+ var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
SetAvailableDecoders(result.Item1);
SetAvailableEncoders(result.Item2);
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ if (EnableEncoderFontFile)
{
var directory = Path.GetDirectoryName(FFMpegPath);
@@ -179,6 +247,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
ConfigureEncoderPaths();
+ if (_hasExternalEncoder)
+ {
+ LogPaths();
+ return;
+ }
+
// If the path was passed in, save it into config now.
var encodingOptions = GetEncodingOptions();
var appPath = encodingOptions.EncoderAppPath;
@@ -188,7 +262,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(valueToSave))
{
// if using system variable, don't save this.
- if (IsSystemInstalledPath(valueToSave))
+ if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
{
valueToSave = null;
}
@@ -203,6 +277,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
public async Task UpdateEncoderPath(string path, string pathType)
{
+ if (_hasExternalEncoder)
+ {
+ return;
+ }
+
Tuple<string, string> newPaths;
if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
@@ -218,7 +297,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ArgumentNullException("path");
}
- if (!File.Exists(path) && !Directory.Exists(path))
+ if (!FileSystem.FileExists(path) && !FileSystem.DirectoryExists(path))
{
throw new ResourceNotFoundException();
}
@@ -240,7 +319,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
path = newPaths.Item1;
- if (!ValidateVersion(path))
+ if (!ValidateVersion(path, true))
{
throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
}
@@ -252,13 +331,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
Init();
}
- private bool ValidateVersion(string path)
+ private bool ValidateVersion(string path, bool logOutput)
{
- return new EncoderValidator(_logger).ValidateVersion(path);
+ return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
}
private void ConfigureEncoderPaths()
{
+ if (_hasExternalEncoder)
+ {
+ return;
+ }
+
var appPath = GetEncodingOptions().EncoderAppPath;
if (string.IsNullOrWhiteSpace(appPath))
@@ -287,12 +371,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(appPath))
{
- if (Directory.Exists(appPath))
+ if (FileSystem.DirectoryExists(appPath))
{
return GetPathsFromDirectory(appPath);
}
- if (File.Exists(appPath))
+ if (FileSystem.FileExists(appPath))
{
return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
}
@@ -306,7 +390,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string encoderPath = null;
string probePath = null;
- if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath))
+ if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
{
encoderPath = _originalFFMpegPath;
probePath = _originalFFProbePath;
@@ -314,7 +398,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.IsNullOrWhiteSpace(encoderPath))
{
- if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe"))
+ if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
{
encoderPath = "ffmpeg";
probePath = "ffprobe";
@@ -328,16 +412,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
// Since we can't predict the file extension, first try directly within the folder
// If that doesn't pan out, then do a recursive search
- var files = Directory.GetFiles(path);
+ var files = FileSystem.GetFilePaths(path);
var excludeExtensions = new[] { ".c" };
var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
- if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath))
+ if (string.IsNullOrWhiteSpace(ffmpegPath) || !FileSystem.FileExists(ffmpegPath))
{
- files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
+ files = FileSystem.GetFilePaths(path, true);
ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
@@ -352,7 +436,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private string GetProbePathFromEncoderPath(string appPath)
{
- return Directory.GetFiles(Path.GetDirectoryName(appPath))
+ return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath))
.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
}
@@ -432,7 +516,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (request.AnalyzeDurationSections > 0)
{
analyzeDuration = "-analyzeduration " +
- (request.AnalyzeDurationSections*1000000).ToString(CultureInfo.InvariantCulture);
+ (request.AnalyzeDurationSections * 1000000).ToString(CultureInfo.InvariantCulture);
}
else
{
@@ -495,7 +579,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="videoType">Type of the video.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns>
- /// <exception cref="System.ApplicationException">ffprobe failed - streams and format are both null.</exception>
private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
string primaryPath,
MediaProtocol protocol,
@@ -509,31 +592,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format"
: "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format";
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- RedirectStandardOutput = true,
- //RedirectStandardError = true,
- RedirectStandardInput = false,
- FileName = FFProbePath,
- Arguments = string.Format(args,
- probeSizeArgument, inputPath).Trim(),
+ CreateNoWindow = true,
+ UseShellExecute = false,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- },
+ // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
+ RedirectStandardOutput = true,
+ FileName = FFProbePath,
+ Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
+ IsHidden = true,
+ ErrorDialog = false,
EnableRaisingEvents = true
- };
+ });
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- using (var processWrapper = new ProcessWrapper(process, this, _logger, false))
+ using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -558,7 +634,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (result.streams == null && result.format == null)
{
- throw new ApplicationException("ffprobe failed - streams and format are both null.");
+ throw new Exception("ffprobe failed - streams and format are both null.");
}
if (result.streams != null)
@@ -595,7 +671,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
catch
{
- StopProcess(processWrapper, 100, true);
+ StopProcess(processWrapper, 100);
throw;
}
@@ -644,31 +720,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
var args = "{0} -i {1} -map 0:v:{2} -an -filter:v idet -frames:v 500 -an -f null /dev/null";
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- //RedirectStandardOutput = true,
- RedirectStandardError = true,
- RedirectStandardInput = false,
- FileName = FFMpegPath,
- Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(),
+ CreateNoWindow = true,
+ UseShellExecute = false,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- },
+ // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
+ RedirectStandardError = true,
+ FileName = FFMpegPath,
+ Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(),
+ IsHidden = true,
+ ErrorDialog = false,
EnableRaisingEvents = true
- };
+ });
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var idetFoundInterlaced = false;
- using (var processWrapper = new ProcessWrapper(process, this, _logger, false))
+ using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
try
{
@@ -711,7 +781,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
catch
{
- StopProcess(processWrapper, 100, true);
+ StopProcess(processWrapper, 100);
throw;
}
@@ -864,7 +934,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
- Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
+ FileSystem.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
// This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
@@ -898,7 +968,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase);
- var thumbnail = enableThumbnail ? ",thumbnail=30" : string.Empty;
+ var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
@@ -916,22 +986,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
}
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = FFMpegPath,
- Arguments = args,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- }
- };
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = FFMpegPath,
+ Arguments = args,
+ IsHidden = true,
+ ErrorDialog = false
+ });
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- using (var processWrapper = new ProcessWrapper(process, this, _logger, false))
+ using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -944,14 +1011,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
if (timeoutMs <= 0)
{
- timeoutMs = Environment.Is64BitOperatingSystem ? (Environment.ProcessorCount > 2 ? 14000 : 20000) : 40000;
+ timeoutMs = DefaultImageExtractionTimeoutMs;
}
ranToCompletion = process.WaitForExit(timeoutMs);
if (!ranToCompletion)
{
- StopProcess(processWrapper, 1000, false);
+ StopProcess(processWrapper, 1000);
}
}
@@ -961,7 +1028,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
- var file = new FileInfo(tempExtractPath);
+ var file = FileSystem.GetFileInfo(tempExtractPath);
if (exitCode == -1 || !file.Exists || file.Length == 0)
{
@@ -969,7 +1036,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.Error(msg);
- throw new ApplicationException(msg);
+ throw new Exception(msg);
}
return tempExtractPath;
@@ -1022,19 +1089,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
args = probeSize + " " + args;
}
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = FFMpegPath,
- Arguments = args,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false,
- RedirectStandardInput = true
- }
- };
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = FFMpegPath,
+ Arguments = args,
+ IsHidden = true,
+ ErrorDialog = false
+ });
_logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
@@ -1042,7 +1105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool ranToCompletion = false;
- using (var processWrapper = new ProcessWrapper(process, this, _logger, true))
+ using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
try
{
@@ -1065,7 +1128,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
cancellationToken.ThrowIfCancellationRequested();
- var jpegCount = Directory.GetFiles(targetDirectory)
+ var jpegCount = FileSystem.GetFilePaths(targetDirectory)
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
isResponsive = (jpegCount > lastCount);
@@ -1074,7 +1137,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!ranToCompletion)
{
- StopProcess(processWrapper, 1000, false);
+ StopProcess(processWrapper, 1000);
}
}
finally
@@ -1090,7 +1153,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.Error(msg);
- throw new ApplicationException(msg);
+ throw new Exception(msg);
}
}
}
@@ -1107,7 +1170,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
LibraryManager,
SessionManager,
SubtitleEncoder(),
- MediaSourceManager())
+ MediaSourceManager(),
+ _processFactory)
.Start(options, progress, cancellationToken).ConfigureAwait(false);
await job.TaskCompletionSource.Task.ConfigureAwait(false);
@@ -1127,7 +1191,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
LibraryManager,
SessionManager,
SubtitleEncoder(),
- MediaSourceManager())
+ MediaSourceManager(),
+ _processFactory)
.Start(options, progress, cancellationToken).ConfigureAwait(false);
await job.TaskCompletionSource.Task.ConfigureAwait(false);
@@ -1144,40 +1209,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
_runningProcesses.Add(process);
}
}
- private void StopProcess(ProcessWrapper process, int waitTimeMs, bool enableForceKill)
+ private void StopProcess(ProcessWrapper process, int waitTimeMs)
{
try
{
- _logger.Info("Killing ffmpeg process");
-
- if (process.IsRedirectingStdin)
+ if (process.Process.WaitForExit(waitTimeMs))
{
- try
- {
- process.Process.StandardInput.WriteLine("q");
- }
- catch (Exception)
- {
- _logger.Error("Error sending q command to process");
- }
+ return;
}
+ }
+ catch (Exception ex)
+ {
+ _logger.Error("Error in WaitForExit", ex);
+ }
- try
- {
- if (process.Process.WaitForExit(waitTimeMs))
- {
- return;
- }
- }
- catch (Exception ex)
- {
- _logger.Error("Error in WaitForExit", ex);
- }
+ try
+ {
+ _logger.Info("Killing ffmpeg process");
- if (enableForceKill)
- {
- process.Process.Kill();
- }
+ process.Process.Kill();
}
catch (Exception ex)
{
@@ -1198,14 +1248,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
if (!process.HasExited)
{
- StopProcess(process, 500, true);
+ StopProcess(process, 500);
}
}
}
public string EscapeSubtitleFilterPath(string path)
{
- return path.Replace('\\', '/').Replace(":/", "\\:/").Replace("'", "'\\\\\\''");
+ // https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping
+ // We need to double escape
+
+ return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
}
/// <summary>
@@ -1231,25 +1284,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
private class ProcessWrapper : IDisposable
{
- public readonly Process Process;
+ public readonly IProcess Process;
public bool HasExited;
public int? ExitCode;
private readonly MediaEncoder _mediaEncoder;
private readonly ILogger _logger;
- public bool IsRedirectingStdin { get; private set; }
- public ProcessWrapper(Process process, MediaEncoder mediaEncoder, ILogger logger, bool isRedirectingStdin)
+ public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger)
{
Process = process;
_mediaEncoder = mediaEncoder;
_logger = logger;
Process.Exited += Process_Exited;
- IsRedirectingStdin = isRedirectingStdin;
}
void Process_Exited(object sender, EventArgs e)
{
- var process = (Process)sender;
+ var process = (IProcess)sender;
HasExited = true;
@@ -1269,7 +1320,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
DisposeProcess(process);
}
- private void DisposeProcess(Process process)
+ private void DisposeProcess(IProcess process)
{
try
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
index 457fbe2c2..cbbca479a 100644
--- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
@@ -8,13 +8,13 @@ using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Model.Diagnostics;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class VideoEncoder : BaseEncoder
{
- public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
+ public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory)
{
}
@@ -191,5 +191,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
get { return true; }
}
+
}
} \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 1b5599577..63e789a59 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.MediaEncoding</RootNamespace>
<AssemblyName>MediaBrowser.MediaEncoding</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
- <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -36,28 +37,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
- <Reference Include="BDInfo">
- <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\BDInfo.dll</HintPath>
- </Reference>
- <Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath>
- </Reference>
- <Reference Include="DvdLib">
- <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\DvdLib.dll</HintPath>
- </Reference>
- <Reference Include="Patterns.Logging">
- <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.Core" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
- <Reference Include="UniversalDetector">
- <HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
+ <Reference Include="UniversalDetector, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll</HintPath>
+ <Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -80,9 +62,11 @@
<Compile Include="Probing\InternalMediaInfoResult.cs" />
<Compile Include="Probing\ProbeResultNormalizer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Subtitles\ConfigurationExtension.cs" />
<Compile Include="Subtitles\ISubtitleParser.cs" />
<Compile Include="Subtitles\ISubtitleWriter.cs" />
<Compile Include="Subtitles\JsonWriter.cs" />
+ <Compile Include="Subtitles\OpenSubtitleDownloader.cs" />
<Compile Include="Subtitles\ParserValues.cs" />
<Compile Include="Subtitles\SrtParser.cs" />
<Compile Include="Subtitles\SrtWriter.cs" />
@@ -93,6 +77,10 @@
<Compile Include="Subtitles\VttWriter.cs" />
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\BDInfo\BDInfo.csproj">
+ <Project>{88ae38df-19d7-406f-a6a9-09527719a21e}</Project>
+ <Name>BDInfo</Name>
+ </ProjectReference>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
@@ -105,14 +93,15 @@
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj">
+ <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project>
+ <Name>OpenSubtitlesHandler</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
- <ItemGroup>
- <EmbeddedResource Include="Probing\whitelist.txt" />
- </ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets
new file mode 100644
index 000000000..e69ce0e64
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Target Name="EmitMSBuildWarning" BeforeTargets="Build">
+ <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
+ </Target>
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index b8c110e14..de055146a 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -8,10 +8,11 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
-using CommonIO;
+using MediaBrowser.Model.IO;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
@@ -22,9 +23,9 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private readonly IMemoryStreamProvider _memoryStreamProvider;
+ private readonly IMemoryStreamFactory _memoryStreamProvider;
- public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider)
+ public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -187,7 +188,13 @@ namespace MediaBrowser.MediaEncoding.Probing
private void FetchFromItunesInfo(string xml, MediaInfo info)
{
// Make things simpler and strip out the dtd
- xml = xml.Substring(xml.IndexOf("<plist", StringComparison.OrdinalIgnoreCase));
+ var plistIndex = xml.IndexOf("<plist", StringComparison.OrdinalIgnoreCase);
+
+ if (plistIndex != -1)
+ {
+ xml = xml.Substring(plistIndex);
+ }
+
xml = "<?xml version=\"1.0\"?>" + xml;
// <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n
@@ -195,44 +202,63 @@ namespace MediaBrowser.MediaEncoding.Probing
{
using (var streamReader = new StreamReader(stream))
{
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader))
+ try
{
- reader.MoveToContent();
-
- // Loop through each element
- while (reader.Read())
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader))
{
- if (reader.NodeType == XmlNodeType.Element)
+ reader.MoveToContent();
+ reader.Read();
+
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
- switch (reader.Name)
+ if (reader.NodeType == XmlNodeType.Element)
{
- case "dict":
- using (var subtree = reader.ReadSubtree())
- {
- ReadFromDictNode(subtree, info);
- }
- break;
- default:
- reader.Skip();
- break;
+ switch (reader.Name)
+ {
+ case "dict":
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
+ using (var subtree = reader.ReadSubtree())
+ {
+ ReadFromDictNode(subtree, info);
+ }
+ break;
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ else
+ {
+ reader.Read();
}
}
}
}
+ catch (XmlException)
+ {
+ // I've seen probe examples where the iTunMOVI value is just "<"
+ // So we should not allow this to fail the entire probing operation
+ }
}
}
}
private void ReadFromDictNode(XmlReader reader, MediaInfo info)
{
- reader.MoveToContent();
-
string currentKey = null;
List<NameValuePair> pairs = new List<NameValuePair>();
+ reader.MoveToContent();
+ reader.Read();
+
// Loop through each element
- while (reader.Read())
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
@@ -258,9 +284,14 @@ namespace MediaBrowser.MediaEncoding.Probing
}
break;
case "array":
- if (!string.IsNullOrWhiteSpace(currentKey))
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
+ using (var subtree = reader.ReadSubtree())
{
- using (var subtree = reader.ReadSubtree())
+ if (!string.IsNullOrWhiteSpace(currentKey))
{
pairs.AddRange(ReadValueArray(subtree));
}
@@ -271,23 +302,35 @@ namespace MediaBrowser.MediaEncoding.Probing
break;
}
}
+ else
+ {
+ reader.Read();
+ }
}
}
private List<NameValuePair> ReadValueArray(XmlReader reader)
{
- reader.MoveToContent();
List<NameValuePair> pairs = new List<NameValuePair>();
+ reader.MoveToContent();
+ reader.Read();
+
// Loop through each element
- while (reader.Read())
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "dict":
+
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
using (var subtree = reader.ReadSubtree())
{
var dict = GetNameValuePair(subtree);
@@ -302,6 +345,10 @@ namespace MediaBrowser.MediaEncoding.Probing
break;
}
}
+ else
+ {
+ reader.Read();
+ }
}
return pairs;
@@ -359,13 +406,14 @@ namespace MediaBrowser.MediaEncoding.Probing
private NameValuePair GetNameValuePair(XmlReader reader)
{
- reader.MoveToContent();
-
string name = null;
string value = null;
+ reader.MoveToContent();
+ reader.Read();
+
// Loop through each element
- while (reader.Read())
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
@@ -382,6 +430,10 @@ namespace MediaBrowser.MediaEncoding.Probing
break;
}
}
+ else
+ {
+ reader.Read();
+ }
}
if (string.IsNullOrWhiteSpace(name) ||
@@ -780,24 +832,24 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
- if (!string.IsNullOrWhiteSpace(conductor))
- {
- foreach (var person in Split(conductor, false))
- {
- audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
- }
- }
-
- var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
+ //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
+ //if (!string.IsNullOrWhiteSpace(conductor))
+ //{
+ // foreach (var person in Split(conductor, false))
+ // {
+ // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
+ // }
+ //}
+
+ //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
+ //if (!string.IsNullOrWhiteSpace(lyricist))
+ //{
+ // foreach (var person in Split(lyricist, false))
+ // {
+ // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
+ // }
+ //}
- if (!string.IsNullOrWhiteSpace(lyricist))
- {
- foreach (var person in Split(lyricist, false))
- {
- audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
- }
- }
// Check for writer some music is tagged that way as alternative to composer/lyricist
var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
@@ -972,27 +1024,10 @@ namespace MediaBrowser.MediaEncoding.Probing
{
if (_splitWhiteList == null)
{
- var file = GetType().Namespace + ".whitelist.txt";
-
- using (var stream = GetType().Assembly.GetManifestResourceStream(file))
- {
- using (var reader = new StreamReader(stream))
- {
- var list = new List<string>();
-
- while (!reader.EndOfStream)
+ _splitWhiteList = new List<string>
{
- var val = reader.ReadLine();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- list.Add(val);
- }
- }
-
- _splitWhiteList = list;
- }
- }
+ "AC/DC"
+ };
}
return _splitWhiteList;
@@ -1247,7 +1282,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
var packetBuffer = new byte['Å'];
- using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+ using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
{
fs.Read(packetBuffer, 0, packetBuffer.Length);
}
diff --git a/MediaBrowser.MediaEncoding/Probing/whitelist.txt b/MediaBrowser.MediaEncoding/Probing/whitelist.txt
deleted file mode 100644
index 1fd366551..000000000
--- a/MediaBrowser.MediaEncoding/Probing/whitelist.txt
+++ /dev/null
@@ -1 +0,0 @@
-AC/DC \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs
new file mode 100644
index 000000000..973c653a4
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Providers;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ public static class ConfigurationExtension
+ {
+ public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager)
+ {
+ return manager.GetConfiguration<SubtitleOptions>("subtitles");
+ }
+ }
+
+ public class SubtitleConfigurationFactory : IConfigurationFactory
+ {
+ public IEnumerable<ConfigurationStore> GetConfigurations()
+ {
+ return new List<ConfigurationStore>
+ {
+ new ConfigurationStore
+ {
+ Key = "subtitles",
+ ConfigurationType = typeof (SubtitleOptions)
+ }
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs
new file mode 100644
index 000000000..e26a6392c
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs
@@ -0,0 +1,349 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.Serialization;
+using OpenSubtitlesHandler;
+
+namespace MediaBrowser.MediaEncoding.Subtitles
+{
+ public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable
+ {
+ private readonly ILogger _logger;
+ private readonly IHttpClient _httpClient;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ private readonly IServerConfigurationManager _config;
+ private readonly IEncryptionManager _encryption;
+
+ private readonly IJsonSerializer _json;
+ private readonly IFileSystem _fileSystem;
+
+ public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem)
+ {
+ _logger = logManager.GetLogger(GetType().Name);
+ _httpClient = httpClient;
+ _config = config;
+ _encryption = encryption;
+ _json = json;
+ _fileSystem = fileSystem;
+
+ _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating;
+
+ Utilities.HttpClient = httpClient;
+ OpenSubtitles.SetUserAgent("mediabrowser.tv");
+ }
+
+ private const string PasswordHashPrefix = "h:";
+ void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e)
+ {
+ if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ var options = (SubtitleOptions)e.NewConfiguration;
+
+ if (options != null &&
+ !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) &&
+ !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash);
+ }
+ }
+
+ private string EncryptPassword(string password)
+ {
+ return PasswordHashPrefix + _encryption.EncryptString(password);
+ }
+
+ private string DecryptPassword(string password)
+ {
+ if (password == null ||
+ !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Empty;
+ }
+
+ return _encryption.DecryptString(password.Substring(2));
+ }
+
+ public string Name
+ {
+ get { return "Open Subtitles"; }
+ }
+
+ private SubtitleOptions GetOptions()
+ {
+ return _config.GetSubtitleConfiguration();
+ }
+
+ public IEnumerable<VideoContentType> SupportedMediaTypes
+ {
+ get
+ {
+ var options = GetOptions();
+
+ if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) ||
+ string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash))
+ {
+ return new VideoContentType[] { };
+ }
+
+ return new[] { VideoContentType.Episode, VideoContentType.Movie };
+ }
+ }
+
+ public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken)
+ {
+ return GetSubtitlesInternal(id, GetOptions(), cancellationToken);
+ }
+
+ private DateTime _lastRateLimitException;
+ private async Task<SubtitleResponse> GetSubtitlesInternal(string id,
+ SubtitleOptions options,
+ CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(id))
+ {
+ throw new ArgumentNullException("id");
+ }
+
+ var idParts = id.Split(new[] { '-' }, 3);
+
+ var format = idParts[0];
+ var language = idParts[1];
+ var ossId = idParts[2];
+
+ var downloadsList = new[] { int.Parse(ossId, _usCulture) };
+
+ await Login(cancellationToken).ConfigureAwait(false);
+
+ if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1)
+ {
+ throw new Exception("OpenSubtitles rate limit reached");
+ }
+
+ var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false);
+
+ if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ _lastRateLimitException = DateTime.UtcNow;
+ throw new Exception("OpenSubtitles rate limit reached");
+ }
+
+ if (!(resultDownLoad is MethodResponseSubtitleDownload))
+ {
+ throw new Exception("Invalid response type");
+ }
+
+ var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results;
+
+ _lastRateLimitException = DateTime.MinValue;
+
+ if (results.Count == 0)
+ {
+ var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}",
+ ossId,
+ resultDownLoad.Name ?? string.Empty,
+ resultDownLoad.Status ?? string.Empty,
+ resultDownLoad.Message ?? string.Empty);
+
+ throw new ResourceNotFoundException(msg);
+ }
+
+ var data = Convert.FromBase64String(results.First().Data);
+
+ return new SubtitleResponse
+ {
+ Format = format,
+ Language = language,
+
+ Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data)))
+ };
+ }
+
+ private DateTime _lastLogin;
+ private async Task Login(CancellationToken cancellationToken)
+ {
+ if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60)
+ {
+ return;
+ }
+
+ var options = GetOptions();
+
+ var user = options.OpenSubtitlesUsername ?? string.Empty;
+ var password = DecryptPassword(options.OpenSubtitlesPasswordHash);
+
+ var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false);
+
+ if (!(loginResponse is MethodResponseLogIn))
+ {
+ throw new Exception("Authentication to OpenSubtitles failed.");
+ }
+
+ _lastLogin = DateTime.UtcNow;
+ }
+
+ public async Task<IEnumerable<NameIdPair>> GetSupportedLanguages(CancellationToken cancellationToken)
+ {
+ await Login(cancellationToken).ConfigureAwait(false);
+
+ var result = OpenSubtitles.GetSubLanguages("en");
+ if (!(result is MethodResponseGetSubLanguages))
+ {
+ _logger.Error("Invalid response type");
+ return new List<NameIdPair>();
+ }
+
+ var results = ((MethodResponseGetSubLanguages)result).Languages;
+
+ return results.Select(i => new NameIdPair
+ {
+ Name = i.LanguageName,
+ Id = i.SubLanguageID
+ });
+ }
+
+ private string NormalizeLanguage(string language)
+ {
+ // Problem with Greek subtitle download #1349
+ if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase))
+ {
+
+ return "ell";
+ }
+
+ return language;
+ }
+
+ public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken)
+ {
+ var imdbIdText = request.GetProviderId(MetadataProviders.Imdb);
+ long imdbId = 0;
+
+ switch (request.ContentType)
+ {
+ case VideoContentType.Episode:
+ if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName))
+ {
+ _logger.Debug("Episode information missing");
+ return new List<RemoteSubtitleInfo>();
+ }
+ break;
+ case VideoContentType.Movie:
+ if (string.IsNullOrEmpty(request.Name))
+ {
+ _logger.Debug("Movie name missing");
+ return new List<RemoteSubtitleInfo>();
+ }
+ if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId))
+ {
+ _logger.Debug("Imdb id missing");
+ return new List<RemoteSubtitleInfo>();
+ }
+ break;
+ }
+
+ if (string.IsNullOrEmpty(request.MediaPath))
+ {
+ _logger.Debug("Path Missing");
+ return new List<RemoteSubtitleInfo>();
+ }
+
+ await Login(cancellationToken).ConfigureAwait(false);
+
+ var subLanguageId = NormalizeLanguage(request.Language);
+ string hash;
+
+ using (var fileStream = _fileSystem.OpenRead(request.MediaPath))
+ {
+ hash = Utilities.ComputeHash(fileStream);
+ }
+ var fileInfo = _fileSystem.GetFileInfo(request.MediaPath);
+ var movieByteSize = fileInfo.Length;
+ var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : "";
+ var subtitleSearchParameters = request.ContentType == VideoContentType.Episode
+ ? new List<SubtitleSearchParameters> {
+ new SubtitleSearchParameters(subLanguageId,
+ query: request.SeriesName,
+ season: request.ParentIndexNumber.Value.ToString(_usCulture),
+ episode: request.IndexNumber.Value.ToString(_usCulture))
+ }
+ : new List<SubtitleSearchParameters> {
+ new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId),
+ new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId)
+ };
+ var parms = new List<SubtitleSearchParameters> {
+ new SubtitleSearchParameters( subLanguageId,
+ movieHash: hash,
+ movieByteSize: movieByteSize,
+ imdbid: searchImdbId ),
+ };
+ parms.AddRange(subtitleSearchParameters);
+ var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false);
+ if (!(result is MethodResponseSubtitleSearch))
+ {
+ _logger.Error("Invalid response type");
+ return new List<RemoteSubtitleInfo>();
+ }
+
+ Predicate<SubtitleSearchResult> mediaFilter =
+ x =>
+ request.ContentType == VideoContentType.Episode
+ ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) &&
+ int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber &&
+ int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber
+ : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId;
+
+ var results = ((MethodResponseSubtitleSearch)result).Results;
+
+ // Avoid implicitly captured closure
+ var hasCopy = hash;
+
+ return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase)))
+ .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1))
+ .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize))
+ .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture))
+ .ThenByDescending(x => double.Parse(x.SubRating, _usCulture))
+ .Select(i => new RemoteSubtitleInfo
+ {
+ Author = i.UserNickName,
+ Comment = i.SubAuthorComment,
+ CommunityRating = float.Parse(i.SubRating, _usCulture),
+ DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture),
+ Format = i.SubFormat,
+ ProviderName = Name,
+ ThreeLetterISOLanguageName = i.SubLanguageID,
+
+ Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile,
+
+ Name = i.SubFileName,
+ DateCreated = DateTime.Parse(i.SubAddDate, _usCulture),
+ IsHashMatch = i.MovieHash == hasCopy
+
+ }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase));
+ }
+
+ public void Dispose()
+ {
+ _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 6dda49a55..0bbc2d916 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -17,9 +17,9 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Diagnostics;
+using MediaBrowser.Model.Text;
using UniversalDetector;
namespace MediaBrowser.MediaEncoding.Subtitles
@@ -34,9 +34,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IJsonSerializer _json;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IMemoryStreamProvider _memoryStreamProvider;
+ private readonly IMemoryStreamFactory _memoryStreamProvider;
+ private readonly IProcessFactory _processFactory;
+ private readonly ITextEncoding _textEncoding;
- public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamProvider memoryStreamProvider)
+ public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, ITextEncoding textEncoding)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -47,6 +49,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
_memoryStreamProvider = memoryStreamProvider;
+ _processFactory = processFactory;
+ _textEncoding = textEncoding;
}
private string SubtitleCachePath
@@ -137,8 +141,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
.ConfigureAwait(false);
var inputFormat = subtitle.Item2;
+ var writer = TryGetWriter(outputFormat);
- if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase) && TryGetWriter(outputFormat) == null)
+ if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase) && writer == null)
+ {
+ return subtitle.Item1;
+ }
+
+ if (writer == null)
{
return subtitle.Item1;
}
@@ -417,7 +427,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// or
/// outputPath
/// </exception>
- /// <exception cref="System.ApplicationException"></exception>
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
@@ -439,46 +448,29 @@ namespace MediaBrowser.MediaEncoding.Subtitles
encodingParam = " -sub_charenc " + encodingParam;
}
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- RedirectStandardOutput = false,
- RedirectStandardError = true,
- RedirectStandardInput = true,
-
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- }
- };
+ IsHidden = true,
+ ErrorDialog = false
+ });
_logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
-
- var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
- true);
-
try
{
process.Start();
}
catch (Exception ex)
{
- logFileStream.Dispose();
-
_logger.ErrorException("Error starting ffmpeg", ex);
throw;
}
-
- var logTask = process.StandardError.BaseStream.CopyToAsync(logFileStream);
var ranToCompletion = process.WaitForExit(60000);
@@ -488,19 +480,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
_logger.Info("Killing ffmpeg subtitle conversion process");
- process.StandardInput.WriteLine("q");
- process.WaitForExit(1000);
-
- await logTask.ConfigureAwait(false);
+ process.Kill();
}
catch (Exception ex)
{
_logger.ErrorException("Error killing subtitle conversion process", ex);
}
- finally
- {
- logFileStream.Dispose();
- }
}
var exitCode = ranToCompletion ? process.ExitCode : -1;
@@ -533,13 +518,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (failed)
{
- var msg = string.Format("ffmpeg subtitle converted failed for {0}", inputPath);
+ var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath);
_logger.Error(msg);
- throw new ApplicationException(msg);
+ throw new Exception(msg);
}
await SetAssFont(outputPath).ConfigureAwait(false);
+
+ _logger.Info("ffmpeg subtitle conversion succeeded for {0}", inputPath);
}
/// <summary>
@@ -592,48 +579,30 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath,
subtitleStreamIndex, outputCodec, outputPath);
- var process = new Process
+ var process = _processFactory.Create(new ProcessOptions
{
- StartInfo = new ProcessStartInfo
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- RedirectStandardOutput = false,
- RedirectStandardError = true,
- RedirectStandardInput = true,
+ CreateNoWindow = true,
+ UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = processArgs,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- }
- };
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = processArgs,
+ IsHidden = true,
+ ErrorDialog = false
+ });
_logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
-
- var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
- true);
-
try
{
process.Start();
}
catch (Exception ex)
{
- logFileStream.Dispose();
-
_logger.ErrorException("Error starting ffmpeg", ex);
throw;
}
- // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
-
var ranToCompletion = process.WaitForExit(300000);
if (!ranToCompletion)
@@ -642,17 +611,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
_logger.Info("Killing ffmpeg subtitle extraction process");
- process.StandardInput.WriteLine("q");
- process.WaitForExit(1000);
+ process.Kill();
}
catch (Exception ex)
{
_logger.ErrorException("Error killing subtitle extraction process", ex);
}
- finally
- {
- logFileStream.Dispose();
- }
}
var exitCode = ranToCompletion ? process.ExitCode : -1;
@@ -674,10 +638,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
}
- catch (DirectoryNotFoundException)
- {
-
- }
catch (IOException ex)
{
_logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath);
@@ -694,7 +654,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.Error(msg);
- throw new ApplicationException(msg);
+ throw new Exception(msg);
}
else
{
@@ -709,33 +669,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private async Task StartStreamingLog(Stream source, Stream target)
- {
- try
- {
- using (var reader = new StreamReader(source))
- {
- while (!reader.EndOfStream)
- {
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
- var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
-
- await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
- await target.FlushAsync().ConfigureAwait(false);
- }
- }
- }
- catch (ObjectDisposedException)
- {
- // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error reading ffmpeg log", ex);
- }
- }
-
/// <summary>
/// Sets the ass font.
/// </summary>
@@ -748,20 +681,26 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string text;
Encoding encoding;
- using (var reader = new StreamReader(file, true))
+ using (var fileStream = _fileSystem.OpenRead(file))
{
- encoding = reader.CurrentEncoding;
+ using (var reader = new StreamReader(fileStream, true))
+ {
+ encoding = reader.CurrentEncoding;
- text = await reader.ReadToEndAsync().ConfigureAwait(false);
+ text = await reader.ReadToEndAsync().ConfigureAwait(false);
+ }
}
var newText = text.Replace(",Arial,", ",Arial Unicode MS,");
if (!string.Equals(text, newText))
{
- using (var writer = new StreamWriter(file, false, encoding))
+ using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{
- writer.Write(newText);
+ using (var writer = new StreamWriter(fileStream, encoding))
+ {
+ writer.Write(newText);
+ }
}
}
}
@@ -794,7 +733,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
if (protocol == MediaProtocol.File)
{
- if (GetFileEncoding(path).Equals(Encoding.UTF8))
+ var fileEncoding = _textEncoding.GetFileEncoding(path);
+
+ if (fileEncoding != null && fileEncoding.Equals(Encoding.UTF8))
{
return string.Empty;
}
@@ -901,29 +842,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return null;
}
- private Encoding GetFileEncoding(string srcFile)
- {
- // *** Detect byte order mark if any - otherwise assume default
- var buffer = new byte[5];
-
- using (var file = _fileSystem.GetFileStream(srcFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- {
- file.Read(buffer, 0, 5);
- }
-
- if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
- return Encoding.UTF8;
- if (buffer[0] == 0xfe && buffer[1] == 0xff)
- return Encoding.Unicode;
- if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
- return Encoding.UTF32;
- if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
- return Encoding.UTF7;
-
- // It's ok - anything aside from utf is ok since that's what we're looking for
- return Encoding.Default;
- }
-
private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (protocol == MediaProtocol.Http)
@@ -932,7 +850,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
if (protocol == MediaProtocol.File)
{
- return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite);
}
throw new ArgumentOutOfRangeException("protocol");
diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config
index 32a5c213d..bac7840a5 100644
--- a/MediaBrowser.MediaEncoding/packages.config
+++ b/MediaBrowser.MediaEncoding/packages.config
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
- <package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
- <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
+ <package id="UniversalDetector" version="1.0.1" targetFramework="net46" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/project.json b/MediaBrowser.MediaEncoding/project.json
new file mode 100644
index 000000000..fbbe9eaf3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/project.json
@@ -0,0 +1,17 @@
+{
+ "frameworks":{
+ "netstandard1.6":{
+ "dependencies":{
+ "NETStandard.Library":"1.6.0",
+ }
+ },
+ ".NETPortable,Version=v4.5,Profile=Profile7":{
+ "buildOptions": {
+ "define": [ ]
+ },
+ "frameworkAssemblies":{
+
+ }
+ }
+ }
+} \ No newline at end of file