aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs113
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs449
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs77
-rw-r--r--MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs29
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs346
8 files changed, 312 insertions, 713 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 262772959a..3eed891cb3 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
+using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Model.Diagnostics;
@@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_processFactory = processFactory;
}
- public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
+ public (IEnumerable<string> decoders, IEnumerable<string> encoders) GetAvailableCoders(string encoderPath)
{
_logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
@@ -48,6 +48,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.IsNullOrWhiteSpace(output))
{
+ if (logOutput)
+ {
+ _logger.LogError("FFmpeg validation: The process returned no result");
+ }
return false;
}
@@ -55,21 +59,114 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
{
+ if (logOutput)
+ {
+ _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
+ }
return false;
}
- output = " " + output + " ";
+ // The min and max FFmpeg versions required to run jellyfin successfully
+ var minRequired = new Version(4, 0);
+ var maxRequired = new Version(4, 0);
+
+ // Work out what the version under test is
+ var underTest = GetFFmpegVersion(output);
- for (var i = 2013; i <= 2015; i++)
+ if (logOutput)
{
- var yearString = i.ToString(CultureInfo.InvariantCulture);
- if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1)
+ _logger.LogInformation("FFmpeg validation: Found ffmpeg version {0}", underTest != null ? underTest.ToString() : "unknown");
+
+ if (underTest == null) // Version is unknown
{
- return false;
+ if (minRequired.Equals(maxRequired))
+ {
+ _logger.LogWarning("FFmpeg validation: We recommend ffmpeg version {0}", minRequired.ToString());
+ }
+ else
+ {
+ _logger.LogWarning("FFmpeg validation: We recommend a minimum of {0} and maximum of {1}", minRequired.ToString(), maxRequired.ToString());
+ }
}
+ else if (underTest.CompareTo(minRequired) < 0) // Version is below what we recommend
+ {
+ _logger.LogWarning("FFmpeg validation: The minimum recommended ffmpeg version is {0}", minRequired.ToString());
+ }
+ else if (underTest.CompareTo(maxRequired) > 0) // Version is above what we recommend
+ {
+ _logger.LogWarning("FFmpeg validation: The maximum recommended ffmpeg version is {0}", maxRequired.ToString());
+ }
+ else // Version is ok
+ {
+ _logger.LogInformation("FFmpeg validation: Found suitable ffmpeg version");
+ }
+ }
+
+ // underTest shall be null if versions is unknown
+ return (underTest == null) ? false : (underTest.CompareTo(minRequired) >= 0 && underTest.CompareTo(maxRequired) <= 0);
+ }
+
+ /// <summary>
+ /// Using the output from "ffmpeg -version" work out the FFmpeg version.
+ /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
+ /// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
+ /// If that fails then we use one of the main libraries to determine if it's new/older than the latest
+ /// we have stored.
+ /// </summary>
+ /// <param name="output"></param>
+ /// <returns></returns>
+ static private Version GetFFmpegVersion(string output)
+ {
+ // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
+ var match = Regex.Match(output, @"ffmpeg version (\d+\.\d+)");
+
+ if (match.Success)
+ {
+ return new Version(match.Groups[1].Value);
+ }
+ else
+ {
+ // Try and use the individual library versions to determine a FFmpeg version
+ // This lookup table is to be maintained with the following command line:
+ // $ ./ffmpeg.exe -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
+ var lut = new ReadOnlyDictionary<Version, string>
+ (new Dictionary<Version, string>
+ {
+ { new Version("4.1"), "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3," },
+ { new Version("4.0"), "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1," },
+ { new Version("3.4"), "libavutil=55.78,libavcodec=57.107,libavformat=57.83,libavdevice=57.10,libavfilter=6.107,libswscale=4.8,libswresample=2.9,libpostproc=54.7," },
+ { new Version("3.3"), "libavutil=55.58,libavcodec=57.89,libavformat=57.71,libavdevice=57.6,libavfilter=6.82,libswscale=4.6,libswresample=2.7,libpostproc=54.5," },
+ { new Version("3.2"), "libavutil=55.34,libavcodec=57.64,libavformat=57.56,libavdevice=57.1,libavfilter=6.65,libswscale=4.2,libswresample=2.3,libpostproc=54.1," },
+ { new Version("2.8"), "libavutil=54.31,libavcodec=56.60,libavformat=56.40,libavdevice=56.4,libavfilter=5.40,libswscale=3.1,libswresample=1.2,libpostproc=53.3," }
+ });
+
+ // Create a reduced version string and lookup key from dictionary
+ var reducedVersion = GetVersionString(output);
+
+ // Try to lookup the string and return Key, otherwise if not found returns null
+ return lut.FirstOrDefault(x => x.Value == reducedVersion).Key;
+ }
+ }
+
+ /// <summary>
+ /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
+ /// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc."
+ /// </summary>
+ /// <param name="output"></param>
+ /// <returns></returns>
+ static private string GetVersionString(string output)
+ {
+ string pattern = @"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))";
+ RegexOptions options = RegexOptions.Multiline;
+
+ string rc = null;
+
+ foreach (Match m in Regex.Matches(output, pattern, options))
+ {
+ rc += string.Concat(m.Groups["name"], '=', m.Groups["major"], '.', m.Groups["minor"], ',');
}
- return true;
+ return rc;
}
private static readonly string[] requiredDecoders = new[]
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index 44e62446b2..d4aede572b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -6,11 +6,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
public static class EncodingUtils
{
- public static string GetInputArgument(List<string> inputFiles, MediaProtocol protocol)
+ public static string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
{
if (protocol != MediaProtocol.File)
{
- var url = inputFiles.First();
+ var url = inputFiles[0];
return string.Format("\"{0}\"", url);
}
@@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// If there's more than one we'll need to use the concat command
if (inputFiles.Count > 1)
{
- var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray());
+ var files = string.Join("|", inputFiles.Select(NormalizePath));
return string.Format("concat:\"{0}\"", files);
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index d922f1068a..4867c0f859 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -3,25 +3,24 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -32,340 +31,217 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class MediaEncoder : IMediaEncoder, IDisposable
{
/// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// Gets the json serializer.
+ /// Gets the encoder path.
/// </summary>
- /// <value>The json serializer.</value>
- private readonly IJsonSerializer _jsonSerializer;
+ /// <value>The encoder path.</value>
+ public string EncoderPath => FFmpegPath;
/// <summary>
- /// The _thumbnail resource pool
+ /// The location of the discovered FFmpeg tool.
/// </summary>
- private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
-
- public string FFMpegPath { get; private set; }
-
- public string FFProbePath { get; private set; }
+ public FFmpegLocation EncoderLocation { get; private set; }
+ private readonly ILogger _logger;
+ private readonly IJsonSerializer _jsonSerializer;
+ private string FFmpegPath;
+ private string FFprobePath;
protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem;
- protected readonly ILiveTvManager LiveTvManager;
- protected readonly IIsoManager IsoManager;
- protected readonly ILibraryManager LibraryManager;
- protected readonly IChannelManager ChannelManager;
- protected readonly ISessionManager SessionManager;
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
protected readonly Func<IMediaSourceManager> MediaSourceManager;
- private readonly IHttpClient _httpClient;
- private readonly IZipClient _zipClient;
private readonly IProcessFactory _processFactory;
+ private readonly int DefaultImageExtractionTimeoutMs;
+ private readonly string StartupOptionFFmpegPath;
+ private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
- private readonly bool _hasExternalEncoder;
- private readonly string _originalFFMpegPath;
- private readonly string _originalFFProbePath;
- private readonly int DefaultImageExtractionTimeoutMs;
+ private readonly ILocalizationManager _localization;
public MediaEncoder(
ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer,
- string ffMpegPath,
- string ffProbePath,
- bool hasExternalEncoder,
+ string startupOptionsFFmpegPath,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
- ILiveTvManager liveTvManager,
- IIsoManager isoManager,
- ILibraryManager libraryManager,
- IChannelManager channelManager,
- ISessionManager sessionManager,
Func<ISubtitleEncoder> subtitleEncoder,
Func<IMediaSourceManager> mediaSourceManager,
- IHttpClient httpClient,
- IZipClient zipClient,
IProcessFactory processFactory,
- int defaultImageExtractionTimeoutMs)
+ int defaultImageExtractionTimeoutMs,
+ ILocalizationManager localization)
{
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
_jsonSerializer = jsonSerializer;
+ StartupOptionFFmpegPath = startupOptionsFFmpegPath;
ConfigurationManager = configurationManager;
FileSystem = fileSystem;
- LiveTvManager = liveTvManager;
- IsoManager = isoManager;
- LibraryManager = libraryManager;
- ChannelManager = channelManager;
- SessionManager = sessionManager;
SubtitleEncoder = subtitleEncoder;
- MediaSourceManager = mediaSourceManager;
- _httpClient = httpClient;
- _zipClient = zipClient;
_processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
- FFProbePath = ffProbePath;
- FFMpegPath = ffMpegPath;
- _originalFFProbePath = ffProbePath;
- _originalFFMpegPath = ffMpegPath;
- _hasExternalEncoder = hasExternalEncoder;
+ _localization = localization;
}
- public string EncoderLocationType
+ /// <summary>
+ /// Run at startup or if the user removes a Custom path from transcode page.
+ /// Sets global variables FFmpegPath.
+ /// Precedence is: Config > CLI > $PATH
+ /// </summary>
+ public void SetFFmpegPath()
{
- get
+ // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
+ if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
{
- if (_hasExternalEncoder)
- {
- return "External";
- }
-
- if (string.IsNullOrWhiteSpace(FFMpegPath))
+ // 2) Check if the --ffmpeg CLI switch has been given
+ if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
{
- return null;
- }
-
- if (IsSystemInstalledPath(FFMpegPath))
- {
- return "System";
+ // 3) Search system $PATH environment variable for valid FFmpeg
+ if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
+ {
+ EncoderLocation = FFmpegLocation.NotFound;
+ FFmpegPath = null;
+ }
}
-
- return "Custom";
}
- }
- private bool IsSystemInstalledPath(string path)
- {
- if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
- {
- return true;
- }
-
- return false;
- }
-
- public void Init()
- {
- InitPaths();
+ // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
+ var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
+ ConfigurationManager.SaveConfiguration("encoding", config);
- if (!string.IsNullOrWhiteSpace(FFMpegPath))
+ // Only if mpeg path is set, try and set path to probe
+ if (FFmpegPath != null)
{
- var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
+ // Determine a probe path from the mpeg path
+ FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
+
+ // Interrogate to understand what coders are supported
+ var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
SetAvailableDecoders(result.decoders);
SetAvailableEncoders(result.encoders);
}
- }
-
- private void InitPaths()
- {
- ConfigureEncoderPaths();
-
- if (_hasExternalEncoder)
- {
- LogPaths();
- return;
- }
-
- // If the path was passed in, save it into config now.
- var encodingOptions = GetEncodingOptions();
- var appPath = encodingOptions.EncoderAppPath;
- var valueToSave = FFMpegPath;
-
- if (!string.IsNullOrWhiteSpace(valueToSave))
- {
- // if using system variable, don't save this.
- if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
- {
- valueToSave = null;
- }
- }
-
- if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
- {
- encodingOptions.EncoderAppPath = valueToSave;
- ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
- }
+ _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty);
}
+ /// <summary>
+ /// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
+ /// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here.
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="pathType"></param>
public void UpdateEncoderPath(string path, string pathType)
{
- if (_hasExternalEncoder)
- {
- return;
- }
+ string newPath;
_logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
- Tuple<string, string> newPaths;
-
- if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
- {
- path = "ffmpeg";
-
- newPaths = TestForInstalledVersions();
- }
- else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
{
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- if (!File.Exists(path) && !Directory.Exists(path))
- {
- throw new ResourceNotFoundException();
- }
- newPaths = GetEncoderPaths(path);
+ throw new ArgumentException("Unexpected pathType value");
}
- else
+ else if (string.IsNullOrWhiteSpace(path))
{
- throw new ArgumentException("Unexpected pathType value");
+ // User had cleared the custom path in UI
+ newPath = string.Empty;
}
-
- if (string.IsNullOrWhiteSpace(newPaths.Item1))
+ else if (File.Exists(path))
{
- throw new ResourceNotFoundException("ffmpeg not found");
+ newPath = path;
}
- if (string.IsNullOrWhiteSpace(newPaths.Item2))
+ else if (Directory.Exists(path))
{
- throw new ResourceNotFoundException("ffprobe not found");
+ // Given path is directory, so resolve down to filename
+ newPath = GetEncoderPathFromDirectory(path, "ffmpeg");
}
-
- path = newPaths.Item1;
-
- if (!ValidateVersion(path, true))
+ else
{
- throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
+ throw new ResourceNotFoundException();
}
- var config = GetEncodingOptions();
- config.EncoderAppPath = path;
+ // Write the new ffmpeg path to the xml as <EncoderAppPath>
+ // This ensures its not lost on next startup
+ var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ config.EncoderAppPath = newPath;
ConfigurationManager.SaveConfiguration("encoding", config);
- Init();
+ // Trigger SetFFmpegPath so we validate the new path and setup probe path
+ SetFFmpegPath();
}
- private bool ValidateVersion(string path, bool logOutput)
+ /// <summary>
+ /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
+ /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
+ /// </summary>
+ /// <param name="path">FQPN to test</param>
+ /// <param name="location">Location (External, Custom, System) of tool</param>
+ /// <returns></returns>
+ private bool ValidatePath(string path, FFmpegLocation location)
{
- return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
- }
+ bool rc = false;
- private void ConfigureEncoderPaths()
- {
- if (_hasExternalEncoder)
+ if (!string.IsNullOrEmpty(path))
{
- return;
- }
-
- var appPath = GetEncodingOptions().EncoderAppPath;
-
- if (string.IsNullOrWhiteSpace(appPath))
- {
- appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg");
- }
-
- var newPaths = GetEncoderPaths(appPath);
- if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath))
- {
- newPaths = TestForInstalledVersions();
- }
-
- if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2))
- {
- FFMpegPath = newPaths.Item1;
- FFProbePath = newPaths.Item2;
- }
+ if (File.Exists(path))
+ {
+ rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
- LogPaths();
- }
+ if (!rc)
+ {
+ _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location.ToString(), path);
+ }
- private Tuple<string, string> GetEncoderPaths(string configuredPath)
- {
- var appPath = configuredPath;
+ // ToDo - Enable the ffmpeg validator. At the moment any version can be used.
+ rc = true;
- if (!string.IsNullOrWhiteSpace(appPath))
- {
- if (Directory.Exists(appPath))
- {
- return GetPathsFromDirectory(appPath);
+ FFmpegPath = path;
+ EncoderLocation = location;
}
-
- if (File.Exists(appPath))
+ else
{
- return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
+ _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location.ToString(), path);
}
}
- return new Tuple<string, string>(null, null);
+ return rc;
}
- private Tuple<string, string> TestForInstalledVersions()
+ private string GetEncoderPathFromDirectory(string path, string filename)
{
- string encoderPath = null;
- string probePath = null;
-
- if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
+ try
{
- encoderPath = _originalFFMpegPath;
- probePath = _originalFFProbePath;
- }
+ var files = FileSystem.GetFilePaths(path);
+
+ var excludeExtensions = new[] { ".c" };
- if (string.IsNullOrWhiteSpace(encoderPath))
+ return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase)
+ && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
+ }
+ catch (Exception)
{
- if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
- {
- encoderPath = "ffmpeg";
- probePath = "ffprobe";
- }
+ // Trap all exceptions, like DirNotExists, and return null
+ return null;
}
-
- return new Tuple<string, string>(encoderPath, probePath);
}
- private Tuple<string, string> GetPathsFromDirectory(string path)
+ /// <summary>
+ /// Search the system $PATH environment variable looking for given filename.
+ /// </summary>
+ /// <param name="fileName"></param>
+ /// <returns></returns>
+ private string ExistsOnSystemPath(string filename)
{
- // 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 = FileSystem.GetFilePaths(path);
-
- var excludeExtensions = new[] { ".c" };
+ var values = Environment.GetEnvironmentVariable("PATH");
- 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))
+ foreach (var path in values.Split(Path.PathSeparator))
{
- files = FileSystem.GetFilePaths(path, true);
-
- ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
+ var candidatePath = GetEncoderPathFromDirectory(path, filename);
- if (!string.IsNullOrWhiteSpace(ffmpegPath))
+ if (!string.IsNullOrEmpty(candidatePath))
{
- ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
+ return candidatePath;
}
}
-
- return new Tuple<string, string>(ffmpegPath, ffprobePath);
- }
-
- private string GetProbePathFromEncoderPath(string appPath)
- {
- return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath))
- .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
- }
-
- private void LogPaths()
- {
- _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found");
- _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found");
- }
-
- private EncodingOptions GetEncodingOptions()
- {
- return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ return null;
}
private List<string> _encoders = new List<string>();
@@ -413,12 +289,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
- /// Gets the encoder path.
- /// </summary>
- /// <value>The encoder path.</value>
- public string EncoderPath => FFMpegPath;
-
- /// <summary>
/// Gets the media info.
/// </summary>
/// <param name="request">The request.</param>
@@ -459,10 +329,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="protocol">The protocol.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentException">Unrecognized InputType</exception>
- public string GetInputArgument(string[] inputFiles, MediaProtocol protocol)
- {
- return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol);
- }
+ public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
+ => EncodingUtils.GetInputArgument(inputFiles, protocol);
/// <summary>
/// Gets the media info internal.
@@ -479,8 +347,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
CancellationToken cancellationToken)
{
var args = extractChapters
- ? "{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";
+ ? "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_chapters -show_format"
+ : "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
+ args = string.Format(args, probeSizeArgument, inputPath).Trim();
var process = _processFactory.Create(new ProcessOptions
{
@@ -489,8 +358,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true,
- FileName = FFProbePath,
- Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
+
+ FileName = FFprobePath,
+ Arguments = args,
+
IsHidden = true,
ErrorDialog = false,
@@ -508,43 +379,45 @@ namespace MediaBrowser.MediaEncoding.Encoder
using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
+ _logger.LogDebug("Starting ffprobe with args {Args}", args);
StartProcess(processWrapper);
+ InternalMediaInfoResult result;
try
{
- //process.BeginErrorReadLine();
+ result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>(
+ process.StandardOutput.BaseStream).ConfigureAwait(false);
+ }
+ catch
+ {
+ StopProcess(processWrapper, 100);
- var result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>(process.StandardOutput.BaseStream).ConfigureAwait(false);
+ throw;
+ }
- if (result == null || (result.streams == null && result.format == null))
- {
- throw new Exception("ffprobe failed - streams and format are both null.");
- }
+ if (result == null || (result.streams == null && result.format == null))
+ {
+ throw new Exception("ffprobe failed - streams and format are both null.");
+ }
- if (result.streams != null)
+ if (result.streams != null)
+ {
+ // Normalize aspect ratio if invalid
+ foreach (var stream in result.streams)
{
- // Normalize aspect ratio if invalid
- foreach (var stream in result.streams)
+ if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
- {
- stream.display_aspect_ratio = string.Empty;
- }
- if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
- {
- stream.sample_aspect_ratio = string.Empty;
- }
+ stream.display_aspect_ratio = string.Empty;
}
- }
- return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
+ if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.sample_aspect_ratio = string.Empty;
+ }
+ }
}
- catch
- {
- StopProcess(processWrapper, 100);
- throw;
- }
+ return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
}
}
@@ -691,10 +564,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
- FileName = FFMpegPath,
+ FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
- ErrorDialog = false
+ ErrorDialog = false,
+ EnableRaisingEvents = true
});
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -813,10 +687,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
- FileName = FFMpegPath,
+ FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
- ErrorDialog = false
+ ErrorDialog = false,
+ EnableRaisingEvents = true
});
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 68b8bd4fae..e4757543ea 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -14,7 +14,6 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
- <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 5099ccb2aa..54d02fc9f7 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -20,11 +21,13 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
- public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem)
+ public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization)
{
_logger = logger;
_fileSystem = fileSystem;
+ _localization = localization;
}
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
@@ -246,52 +249,49 @@ namespace MediaBrowser.MediaEncoding.Probing
// <?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
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
+ using (var streamReader = new StreamReader(stream))
{
- using (var streamReader = new StreamReader(stream))
+ try
{
- try
+ using (var reader = XmlReader.Create(streamReader))
{
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader))
- {
- reader.MoveToContent();
- reader.Read();
+ reader.MoveToContent();
+ reader.Read();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
{
- if (reader.NodeType == XmlNodeType.Element)
+ switch (reader.Name)
{
- 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();
+ 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
- }
+ }
+ 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
}
}
}
@@ -599,6 +599,9 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
+ stream.localizedUndefined = _localization.GetLocalizedString("Undefined");
+ stream.localizedDefault = _localization.GetLocalizedString("Default");
+ stream.localizedForced = _localization.GetLocalizedString("Forced");
}
else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs
index 6ecdf89bc1..a9491374bd 100644
--- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs
+++ b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
-[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
+[assembly: AssemblyProduct("Jellyfin Server")]
+[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs
deleted file mode 100644
index 92544f4f62..0000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-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
deleted file mode 100644
index 6a5162b8d6..0000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs
+++ /dev/null
@@ -1,346 +0,0 @@
-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.Providers;
-using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
-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(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem)
- {
- _logger = loggerFactory.CreateLogger(GetType().Name);
- _httpClient = httpClient;
- _config = config;
- _encryption = encryption;
- _json = json;
- _fileSystem = fileSystem;
-
- _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating;
-
- Utilities.HttpClient = httpClient;
- OpenSubtitles.SetUserAgent("jellyfin");
- }
-
- 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 => "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(nameof(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 RateLimitExceededException("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 RateLimitExceededException("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.LogError("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.LogDebug("Episode information missing");
- return new List<RemoteSubtitleInfo>();
- }
- break;
- case VideoContentType.Movie:
- if (string.IsNullOrEmpty(request.Name))
- {
- _logger.LogDebug("Movie name missing");
- return new List<RemoteSubtitleInfo>();
- }
- if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId))
- {
- _logger.LogDebug("Imdb id missing");
- return new List<RemoteSubtitleInfo>();
- }
- break;
- }
-
- if (string.IsNullOrEmpty(request.MediaPath))
- {
- _logger.LogDebug("Path Missing");
- return new List<RemoteSubtitleInfo>();
- }
-
- await Login(cancellationToken).ConfigureAwait(false);
-
- var subLanguageId = NormalizeLanguage(request.Language);
- string hash;
-
- using (var fileStream = File.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.LogError("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;
- }
- }
-}