mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 17:14:42 +01:00
Merge branch 'master' into tonemap
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
@@ -238,11 +240,11 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
|
||||
filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D");
|
||||
filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D");
|
||||
filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var prefix = filename.Substring(0, 1);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BDInfo.IO;
|
||||
@@ -5,7 +7,7 @@ using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.BdInfo
|
||||
{
|
||||
class BdInfoDirectoryInfo : IDirectoryInfo
|
||||
public class BdInfoDirectoryInfo : IDirectoryInfo
|
||||
{
|
||||
private readonly IFileSystem _fileSystem = null;
|
||||
|
||||
@@ -43,25 +45,32 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||
|
||||
public IDirectoryInfo[] GetDirectories()
|
||||
{
|
||||
return Array.ConvertAll(_fileSystem.GetDirectories(_impl.FullName).ToArray(),
|
||||
return Array.ConvertAll(
|
||||
_fileSystem.GetDirectories(_impl.FullName).ToArray(),
|
||||
x => new BdInfoDirectoryInfo(_fileSystem, x));
|
||||
}
|
||||
|
||||
public IFileInfo[] GetFiles()
|
||||
{
|
||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName).ToArray(),
|
||||
return Array.ConvertAll(
|
||||
_fileSystem.GetFiles(_impl.FullName).ToArray(),
|
||||
x => new BdInfoFileInfo(x));
|
||||
}
|
||||
|
||||
public IFileInfo[] GetFiles(string searchPattern)
|
||||
{
|
||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
|
||||
return Array.ConvertAll(
|
||||
_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
|
||||
x => new BdInfoFileInfo(x));
|
||||
}
|
||||
|
||||
public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
|
||||
{
|
||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false,
|
||||
return Array.ConvertAll(
|
||||
_fileSystem.GetFiles(
|
||||
_impl.FullName,
|
||||
new[] { searchPattern },
|
||||
false,
|
||||
searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
|
||||
x => new BdInfoFileInfo(x));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BdInfoExaminer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
public BdInfoExaminer(IFileSystem fileSystem)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.BdInfo
|
||||
{
|
||||
class BdInfoFileInfo : BDInfo.IO.IFileInfo
|
||||
public class BdInfoFileInfo : BDInfo.IO.IFileInfo
|
||||
{
|
||||
FileSystemMetadata _impl = null;
|
||||
private FileSystemMetadata _impl = null;
|
||||
|
||||
public BdInfoFileInfo(FileSystemMetadata impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public string Name => _impl.Name;
|
||||
|
||||
@@ -17,14 +24,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||
|
||||
public bool IsDir => _impl.IsDirectory;
|
||||
|
||||
public BdInfoFileInfo(FileSystemMetadata impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public System.IO.Stream OpenRead()
|
||||
{
|
||||
return new FileStream(FullName,
|
||||
return new FileStream(
|
||||
FullName,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Configuration
|
||||
{
|
||||
@@ -17,32 +15,4 @@ namespace MediaBrowser.MediaEncoding.Configuration
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
|
||||
{
|
||||
public EncodingConfigurationStore()
|
||||
{
|
||||
ConfigurationType = typeof(EncodingOptions);
|
||||
Key = "encoding";
|
||||
}
|
||||
|
||||
public void Validate(object oldConfig, object newConfig)
|
||||
{
|
||||
var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} does not exist.",
|
||||
newPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Configuration
|
||||
{
|
||||
public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
|
||||
{
|
||||
public EncodingConfigurationStore()
|
||||
{
|
||||
ConfigurationType = typeof(EncodingOptions);
|
||||
Key = "encoding";
|
||||
}
|
||||
|
||||
public void Validate(object oldConfig, object newConfig)
|
||||
{
|
||||
var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} does not exist.",
|
||||
newPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -12,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
private const string DefaultEncoderPath = "ffmpeg";
|
||||
|
||||
private static readonly string[] requiredDecoders = new[]
|
||||
private static readonly string[] _requiredDecoders = new[]
|
||||
{
|
||||
"h264",
|
||||
"hevc",
|
||||
@@ -55,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"vc1_opencl"
|
||||
};
|
||||
|
||||
private static readonly string[] requiredEncoders = new[]
|
||||
private static readonly string[] _requiredEncoders = new[]
|
||||
{
|
||||
"libx264",
|
||||
"libx265",
|
||||
@@ -85,19 +87,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"hevc_videotoolbox"
|
||||
};
|
||||
|
||||
// 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 -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
|
||||
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
|
||||
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
|
||||
private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
||||
{
|
||||
{ "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) },
|
||||
{ "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) },
|
||||
{ "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, 1) },
|
||||
{ "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(4, 0) },
|
||||
{ "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, 4) },
|
||||
{ "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, 3) },
|
||||
{ "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(3, 2) },
|
||||
{ "libavutil=54.31,libavcodec=56.60,libavformat=56.40,libavdevice=56.4,libavfilter=5.40,libswscale=3.1,libswresample=1.2,libpostproc=53.3,", new Version(2, 8) }
|
||||
{ "libavutil", new Version(56, 14) },
|
||||
{ "libavcodec", new Version(58, 18) },
|
||||
{ "libavformat", new Version(58, 12) },
|
||||
{ "libavdevice", new Version(58, 3) },
|
||||
{ "libavfilter", new Version(7, 16) },
|
||||
{ "libswscale", new Version(5, 1) },
|
||||
{ "libswresample", new Version(3, 1) },
|
||||
{ "libpostproc", new Version(55, 1) }
|
||||
};
|
||||
|
||||
private readonly ILogger _logger;
|
||||
@@ -110,6 +110,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_encoderPath = encoderPath;
|
||||
}
|
||||
|
||||
private enum Codec
|
||||
{
|
||||
Encoder,
|
||||
Decoder
|
||||
}
|
||||
|
||||
// When changing this, also change the minimum library versions in _ffmpegMinimumLibraryVersions
|
||||
public static Version MinVersion { get; } = new Version(4, 0);
|
||||
|
||||
public static Version MaxVersion { get; } = null;
|
||||
@@ -148,32 +155,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// Work out what the version under test is
|
||||
var version = GetFFmpegVersion(versionOutput);
|
||||
|
||||
_logger.LogInformation("Found ffmpeg version {0}", version != null ? version.ToString() : "unknown");
|
||||
_logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
|
||||
|
||||
if (version == null)
|
||||
{
|
||||
if (MinVersion != null && MaxVersion != null) // Version is unknown
|
||||
if (MaxVersion != null) // Version is unknown
|
||||
{
|
||||
if (MinVersion == MaxVersion)
|
||||
{
|
||||
_logger.LogWarning("FFmpeg validation: We recommend ffmpeg version {0}", MinVersion);
|
||||
_logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("FFmpeg validation: We recommend a minimum of {0} and maximum of {1}", MinVersion, MaxVersion);
|
||||
_logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {MaxVersion}", MinVersion, MaxVersion);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (MinVersion != null && version < MinVersion) // Version is below what we recommend
|
||||
else if (version < MinVersion) // Version is below what we recommend
|
||||
{
|
||||
_logger.LogWarning("FFmpeg validation: The minimum recommended ffmpeg version is {0}", MinVersion);
|
||||
_logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
|
||||
return false;
|
||||
}
|
||||
else if (MaxVersion != null && version > MaxVersion) // Version is above what we recommend
|
||||
{
|
||||
_logger.LogWarning("FFmpeg validation: The maximum recommended ffmpeg version is {0}", MaxVersion);
|
||||
_logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -189,13 +200,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <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.
|
||||
/// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
|
||||
/// If that fails then we test the libraries to determine if they're newer than our minimum versions.
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <returns></returns>
|
||||
internal static Version GetFFmpegVersion(string output)
|
||||
/// <param name="output">The output from "ffmpeg -version".</param>
|
||||
/// <returns>The FFmpeg version.</returns>
|
||||
internal 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 n?((?:[0-9]+\.?)+)");
|
||||
@@ -204,45 +214,58 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
return new Version(match.Groups[1].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a reduced version string and lookup key from dictionary
|
||||
var reducedVersion = GetLibrariesVersionString(output);
|
||||
|
||||
// Try to lookup the string and return Key, otherwise if not found returns null
|
||||
return _ffmpegVersionMap.TryGetValue(reducedVersion, out Version version) ? version : null;
|
||||
var versionMap = GetFFmpegLibraryVersions(output);
|
||||
|
||||
var allVersionsValidated = true;
|
||||
|
||||
foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
|
||||
{
|
||||
if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
|
||||
{
|
||||
if (foundVersion >= minimumVersion.Value)
|
||||
{
|
||||
_logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersion.Key, foundVersion, minimumVersion.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {MinimumVersion}", minimumVersion.Key, foundVersion, minimumVersion.Value);
|
||||
allVersionsValidated = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("{Library} version not found", minimumVersion.Key);
|
||||
allVersionsValidated = false;
|
||||
}
|
||||
}
|
||||
|
||||
return allVersionsValidated ? MinVersion : null;
|
||||
}
|
||||
|
||||
/// <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."
|
||||
/// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc.".
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetLibrariesVersionString(string output)
|
||||
/// <param name="output">The 'ffmpeg -version' output.</param>
|
||||
/// <returns>The library names and major.minor version numbers.</returns>
|
||||
private static IReadOnlyDictionary<string, Version> GetFFmpegLibraryVersions(string output)
|
||||
{
|
||||
var rc = new StringBuilder(144);
|
||||
foreach (Match m in Regex.Matches(
|
||||
var map = new Dictionary<string, Version>();
|
||||
|
||||
foreach (Match match in Regex.Matches(
|
||||
output,
|
||||
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
|
||||
RegexOptions.Multiline))
|
||||
{
|
||||
rc.Append(m.Groups["name"])
|
||||
.Append('=')
|
||||
.Append(m.Groups["major"])
|
||||
.Append('.')
|
||||
.Append(m.Groups["minor"])
|
||||
.Append(',');
|
||||
var version = new Version(
|
||||
int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture),
|
||||
int.Parse(match.Groups["minor"].Value, CultureInfo.InvariantCulture));
|
||||
|
||||
map.Add(match.Groups["name"].Value, version);
|
||||
}
|
||||
|
||||
return rc.Length == 0 ? null : rc.ToString();
|
||||
}
|
||||
|
||||
private enum Codec
|
||||
{
|
||||
Encoder,
|
||||
Decoder
|
||||
return map;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetHwaccelTypes()
|
||||
@@ -262,7 +285,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
||||
var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
||||
_logger.LogInformation("Available hwaccel types: {Types}", found);
|
||||
|
||||
return found;
|
||||
@@ -286,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
|
||||
var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
|
||||
|
||||
var found = Regex
|
||||
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
||||
@@ -12,7 +16,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var url = inputFiles[0];
|
||||
|
||||
return string.Format("\"{0}\"", url);
|
||||
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", url);
|
||||
}
|
||||
|
||||
return GetConcatInputArgument(inputFiles);
|
||||
@@ -31,7 +35,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var files = string.Join("|", inputFiles.Select(NormalizePath));
|
||||
|
||||
return string.Format("concat:\"{0}\"", files);
|
||||
return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files);
|
||||
}
|
||||
|
||||
// Determine the input path for video files
|
||||
@@ -45,15 +49,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <returns>System.String.</returns>
|
||||
private static string GetFileInputArgument(string path)
|
||||
{
|
||||
if (path.IndexOf("://") != -1)
|
||||
if (path.IndexOf("://", StringComparison.Ordinal) != -1)
|
||||
{
|
||||
return string.Format("\"{0}\"", path);
|
||||
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path);
|
||||
}
|
||||
|
||||
// Quotes are valid path characters in linux and they need to be escaped here with a leading \
|
||||
path = NormalizePath(path);
|
||||
|
||||
return string.Format("file:\"{0}\"", path);
|
||||
return string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +68,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
// Quotes are valid path characters in linux and they need to be escaped here with a leading \
|
||||
return path.Replace("\"", "\\\"");
|
||||
return path.Replace("\"", "\\\"", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -9,6 +12,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.MediaEncoding.Probing;
|
||||
@@ -19,9 +23,8 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
@@ -35,6 +38,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// </summary>
|
||||
internal const int DefaultImageExtractionTimeout = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// The us culture.
|
||||
/// </summary>
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly ILogger<MediaEncoder> _logger;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
@@ -47,6 +55,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private readonly object _runningProcessesLock = new object();
|
||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
|
||||
// MediaEncoder is registered as a Singleton
|
||||
private readonly JsonSerializerOptions _jsonSerializerOptions;
|
||||
|
||||
private List<string> _encoders = new List<string>();
|
||||
private List<string> _decoders = new List<string>();
|
||||
private List<string> _hwaccels = new List<string>();
|
||||
|
||||
private string _ffmpegPath = string.Empty;
|
||||
private string _ffprobePath;
|
||||
|
||||
@@ -64,6 +79,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_localization = localization;
|
||||
_encodingHelperFactory = encodingHelperFactory;
|
||||
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
|
||||
_jsonSerializerOptions = JsonDefaults.GetOptions();
|
||||
}
|
||||
|
||||
private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
|
||||
@@ -77,7 +93,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <summary>
|
||||
/// Run at startup or if the user removes a Custom path from transcode page.
|
||||
/// Sets global variables FFmpegPath.
|
||||
/// Precedence is: Config > CLI > $PATH
|
||||
/// Precedence is: Config > CLI > $PATH.
|
||||
/// </summary>
|
||||
public void SetFFmpegPath()
|
||||
{
|
||||
@@ -122,8 +138,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// 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>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="pathType">The path type.</param>
|
||||
public void UpdateEncoderPath(string path, string pathType)
|
||||
{
|
||||
string newPath;
|
||||
@@ -168,8 +184,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// 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>
|
||||
/// <param name="location">Location (External, Custom, System) of tool.</param>
|
||||
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
|
||||
private bool ValidatePath(string path, FFmpegLocation location)
|
||||
{
|
||||
bool rc = false;
|
||||
@@ -185,9 +201,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
|
||||
}
|
||||
|
||||
// ToDo - Enable the ffmpeg validator. At the moment any version can be used.
|
||||
rc = true;
|
||||
|
||||
_ffmpegPath = path;
|
||||
EncoderLocation = location;
|
||||
}
|
||||
@@ -221,8 +234,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <summary>
|
||||
/// Search the system $PATH environment variable looking for given filename.
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="fileName">The filename.</param>
|
||||
/// <returns>The full path to the file.</returns>
|
||||
private string ExistsOnSystemPath(string fileName)
|
||||
{
|
||||
var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
|
||||
@@ -246,25 +259,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<string> _encoders = new List<string>();
|
||||
public void SetAvailableEncoders(IEnumerable<string> list)
|
||||
{
|
||||
_encoders = list.ToList();
|
||||
// _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
private List<string> _decoders = new List<string>();
|
||||
public void SetAvailableDecoders(IEnumerable<string> list)
|
||||
{
|
||||
_decoders = list.ToList();
|
||||
// _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
private List<string> _hwaccels = new List<string>();
|
||||
public void SetAvailableHwaccels(IEnumerable<string> list)
|
||||
{
|
||||
_hwaccels = list.ToList();
|
||||
//_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
public bool SupportsEncoder(string encoder)
|
||||
@@ -332,8 +339,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
||||
|
||||
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.MediaSource.Protocol), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters,
|
||||
probeSize, request.MediaType == DlnaProfileType.Audio, request.MediaSource.VideoType, forceEnableLogging, cancellationToken);
|
||||
return GetMediaInfoInternal(
|
||||
GetInputArgument(inputFiles, request.MediaSource.Protocol),
|
||||
request.MediaSource.Path,
|
||||
request.MediaSource.Protocol,
|
||||
extractChapters,
|
||||
probeSize,
|
||||
request.MediaType == DlnaProfileType.Audio,
|
||||
request.MediaSource.VideoType,
|
||||
forceEnableLogging,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -342,7 +357,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <param name="inputFiles">The input files.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentException">Unrecognized InputType</exception>
|
||||
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
||||
public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
|
||||
=> EncodingUtils.GetInputArgument(inputFiles, protocol);
|
||||
|
||||
@@ -350,7 +365,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// Gets the media info internal.
|
||||
/// </summary>
|
||||
/// <returns>Task{MediaInfoResult}.</returns>
|
||||
private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
|
||||
private async Task<MediaInfo> GetMediaInfoInternal(
|
||||
string inputPath,
|
||||
string primaryPath,
|
||||
MediaProtocol protocol,
|
||||
bool extractChapters,
|
||||
@@ -363,7 +379,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
var args = extractChapters
|
||||
? "{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();
|
||||
args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath).Trim();
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
@@ -378,7 +394,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
|
||||
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
},
|
||||
@@ -404,6 +419,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
|
||||
process.StandardOutput.BaseStream,
|
||||
_jsonSerializerOptions,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
@@ -439,11 +455,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The us culture.
|
||||
/// </summary>
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
|
||||
@@ -459,8 +470,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<string> ExtractImage(string[] inputFiles, string container, MediaStream videoStream, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
|
||||
Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
|
||||
private async Task<string> ExtractImage(
|
||||
string[] inputFiles,
|
||||
string container,
|
||||
MediaStream videoStream,
|
||||
int? imageStreamIndex,
|
||||
MediaProtocol protocol,
|
||||
bool isAudio,
|
||||
Video3DFormat? threedFormat,
|
||||
TimeSpan? offset,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||
|
||||
@@ -536,8 +555,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// 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 thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
|
||||
|
||||
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) :
|
||||
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||
var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
|
||||
string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||
|
||||
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||
@@ -554,7 +573,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (offset.HasValue)
|
||||
{
|
||||
args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
|
||||
args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args;
|
||||
}
|
||||
|
||||
if (videoStream != null)
|
||||
@@ -625,7 +644,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (exitCode == -1 || !file.Exists || file.Length == 0)
|
||||
{
|
||||
var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
|
||||
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath);
|
||||
|
||||
_logger.LogError(msg);
|
||||
|
||||
@@ -645,7 +664,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
public string GetTimeParameter(TimeSpan time)
|
||||
{
|
||||
return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
|
||||
return time.ToString(@"hh\:mm\:ss\.fff", _usCulture);
|
||||
}
|
||||
|
||||
public async Task ExtractVideoImagesOnInterval(
|
||||
@@ -662,19 +681,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||
|
||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
|
||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture);
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
var maxWidthParam = maxWidth.Value.ToString(UsCulture);
|
||||
var maxWidthParam = maxWidth.Value.ToString(_usCulture);
|
||||
|
||||
vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
||||
vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
||||
|
||||
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||
|
||||
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||
@@ -774,7 +793,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (exitCode == -1)
|
||||
{
|
||||
var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
|
||||
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument);
|
||||
|
||||
_logger.LogError(msg);
|
||||
|
||||
@@ -840,7 +859,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping
|
||||
// We need to double escape
|
||||
|
||||
return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
|
||||
return path.Replace('\\', '/').Replace(":", "\\:", StringComparison.Ordinal).Replace("'", "'\\\\\\''", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -859,6 +878,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
if (dispose)
|
||||
{
|
||||
StopProcesses();
|
||||
_thumbnailResourcePool.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,4 +28,16 @@
|
||||
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Probing
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing helper methods for working with FFprobe output.
|
||||
/// </summary>
|
||||
public static class FFProbeHelpers
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
@@ -133,7 +133,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
/// </summary>
|
||||
/// <value>The bits_per_raw_sample.</value>
|
||||
[JsonPropertyName("bits_per_raw_sample")]
|
||||
[JsonConverter(typeof(JsonInt32Converter))]
|
||||
public int BitsPerRawSample { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -269,6 +268,10 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
[JsonPropertyName("loro_surmixlev")]
|
||||
public string LoroSurmixlev { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the field_order.
|
||||
/// </summary>
|
||||
/// <value>The field_order.</value>
|
||||
[JsonPropertyName("field_order")]
|
||||
public string FieldOrder { get; set; }
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -16,10 +18,19 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
{
|
||||
public class ProbeResultNormalizer
|
||||
{
|
||||
// When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
|
||||
private const int MaxSubtitleDescriptionExtractionLength = 100;
|
||||
|
||||
private const string ArtistReplaceValue = " | ";
|
||||
|
||||
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
private List<string> _splitWhiteList = null;
|
||||
|
||||
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -31,7 +42,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
var info = new MediaInfo
|
||||
{
|
||||
Path = path,
|
||||
Protocol = protocol
|
||||
Protocol = protocol,
|
||||
VideoType = videoType
|
||||
};
|
||||
|
||||
FFProbeHelpers.NormalizeFFProbeResult(data);
|
||||
@@ -368,7 +380,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
|
||||
private List<NameValuePair> ReadValueArray(XmlReader reader)
|
||||
{
|
||||
|
||||
var pairs = new List<NameValuePair>();
|
||||
|
||||
reader.MoveToContent();
|
||||
@@ -959,50 +970,46 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
|
||||
private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
|
||||
{
|
||||
var peoples = new List<BaseItemPerson>();
|
||||
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
|
||||
if (!string.IsNullOrWhiteSpace(composer))
|
||||
{
|
||||
var peoples = new List<BaseItemPerson>();
|
||||
foreach (var person in Split(composer, false))
|
||||
{
|
||||
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
|
||||
}
|
||||
|
||||
audio.People = peoples.ToArray();
|
||||
}
|
||||
|
||||
// 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 conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
|
||||
if (!string.IsNullOrWhiteSpace(conductor))
|
||||
{
|
||||
foreach (var person in Split(conductor, false))
|
||||
{
|
||||
peoples.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 });
|
||||
// }
|
||||
//}
|
||||
var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
|
||||
if (!string.IsNullOrWhiteSpace(lyricist))
|
||||
{
|
||||
foreach (var person in Split(lyricist, false))
|
||||
{
|
||||
peoples.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");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(writer))
|
||||
{
|
||||
var peoples = new List<BaseItemPerson>();
|
||||
foreach (var person in Split(writer, false))
|
||||
{
|
||||
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
|
||||
}
|
||||
|
||||
audio.People = peoples.ToArray();
|
||||
}
|
||||
|
||||
audio.People = peoples.ToArray();
|
||||
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
|
||||
|
||||
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
|
||||
@@ -1127,8 +1134,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
|
||||
}
|
||||
|
||||
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified val.
|
||||
/// </summary>
|
||||
@@ -1139,7 +1144,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
{
|
||||
// Only use the comma as a delimeter if there are no slashes or pipes.
|
||||
// We want to be careful not to split names that have commas in them
|
||||
var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
|
||||
var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i, StringComparison.Ordinal) != -1) ?
|
||||
_nameDelimiters :
|
||||
new[] { ',' };
|
||||
|
||||
@@ -1148,8 +1153,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
.Select(i => i.Trim());
|
||||
}
|
||||
|
||||
private const string ArtistReplaceValue = " | ";
|
||||
|
||||
private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
|
||||
{
|
||||
if (splitFeaturing)
|
||||
@@ -1179,9 +1182,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
return artistsFound;
|
||||
}
|
||||
|
||||
|
||||
private List<string> _splitWhiteList = null;
|
||||
|
||||
private IEnumerable<string> GetSplitWhitelist()
|
||||
{
|
||||
if (_splitWhiteList == null)
|
||||
@@ -1258,7 +1258,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
|
||||
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'.
|
||||
/// </summary>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="tagName">Name of the tag.</param>
|
||||
@@ -1304,8 +1304,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
return info;
|
||||
}
|
||||
|
||||
private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
|
||||
|
||||
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
|
||||
{
|
||||
if (data.Format == null || data.Format.Tags == null)
|
||||
@@ -1390,8 +1388,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
|
||||
{
|
||||
string[] numbers = subtitle.Split(' ');
|
||||
video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
|
||||
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
|
||||
video.IndexNumber = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[0], CultureInfo.InvariantCulture);
|
||||
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[1], CultureInfo.InvariantCulture);
|
||||
|
||||
description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -13,6 +15,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <inheritdoc />
|
||||
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var trackInfo = new SubtitleTrackInfo();
|
||||
@@ -22,7 +25,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
string line;
|
||||
while (reader.ReadLine() != "[Events]")
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
var headers = ParseFieldHeaders(reader.ReadLine());
|
||||
|
||||
@@ -72,22 +76,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
|
||||
|
||||
var result = new Dictionary<string, int> {
|
||||
{"Start", fields.IndexOf("Start")},
|
||||
{"End", fields.IndexOf("End")},
|
||||
{"Text", fields.IndexOf("Text")}
|
||||
};
|
||||
return result;
|
||||
return new Dictionary<string, int>
|
||||
{
|
||||
{ "Start", fields.IndexOf("Start") },
|
||||
{ "End", fields.IndexOf("End") },
|
||||
{ "Text", fields.IndexOf("Text") }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs
|
||||
/// </summary>
|
||||
private void RemoteNativeFormatting(SubtitleTrackEvent p)
|
||||
{
|
||||
int indexOfBegin = p.Text.IndexOf('{');
|
||||
int indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
|
||||
string pre = string.Empty;
|
||||
while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin)
|
||||
while (indexOfBegin >= 0 && p.Text.IndexOf('}', StringComparison.Ordinal) > indexOfBegin)
|
||||
{
|
||||
string s = p.Text.Substring(indexOfBegin);
|
||||
if (s.StartsWith("{\\an1}", StringComparison.Ordinal) ||
|
||||
@@ -115,10 +116,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
pre = s.Substring(0, 5) + "}";
|
||||
}
|
||||
|
||||
int indexOfEnd = p.Text.IndexOf('}');
|
||||
int indexOfEnd = p.Text.IndexOf('}', StringComparison.Ordinal);
|
||||
p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1);
|
||||
|
||||
indexOfBegin = p.Text.IndexOf('{');
|
||||
indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
p.Text = pre + p.Text;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -8,7 +9,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
/// <summary>
|
||||
/// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
|
||||
/// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
|
||||
/// </summary>
|
||||
public class SsaParser : ISubtitleParser
|
||||
{
|
||||
@@ -50,14 +51,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
eventsStarted = true;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";"))
|
||||
else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";", StringComparison.Ordinal))
|
||||
{
|
||||
// skip comment lines
|
||||
}
|
||||
else if (eventsStarted && line.Trim().Length > 0)
|
||||
{
|
||||
string s = line.Trim().ToLowerInvariant();
|
||||
if (s.StartsWith("format:"))
|
||||
if (s.StartsWith("format:", StringComparison.Ordinal))
|
||||
{
|
||||
if (line.Length > 10)
|
||||
{
|
||||
@@ -103,7 +104,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
string[] splittedLine;
|
||||
|
||||
if (s.StartsWith("dialogue:"))
|
||||
if (s.StartsWith("dialogue:", StringComparison.Ordinal))
|
||||
{
|
||||
splittedLine = line.Substring(10).Split(',');
|
||||
}
|
||||
@@ -179,10 +180,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
// h:mm:ss.cc
|
||||
string[] timeCode = time.Split(':', '.');
|
||||
return new TimeSpan(0, int.Parse(timeCode[0]),
|
||||
int.Parse(timeCode[1]),
|
||||
int.Parse(timeCode[2]),
|
||||
int.Parse(timeCode[3]) * 10).Ticks;
|
||||
return new TimeSpan(
|
||||
0,
|
||||
int.Parse(timeCode[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(timeCode[1], CultureInfo.InvariantCulture),
|
||||
int.Parse(timeCode[2], CultureInfo.InvariantCulture),
|
||||
int.Parse(timeCode[3], CultureInfo.InvariantCulture) * 10).Ticks;
|
||||
}
|
||||
|
||||
private static string GetFormattedText(string text)
|
||||
@@ -191,11 +194,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
for (int i = 0; i < 10; i++) // just look ten times...
|
||||
{
|
||||
if (text.Contains(@"{\fn"))
|
||||
if (text.Contains(@"{\fn", StringComparison.Ordinal))
|
||||
{
|
||||
int start = text.IndexOf(@"{\fn");
|
||||
int start = text.IndexOf(@"{\fn", StringComparison.Ordinal);
|
||||
int end = text.IndexOf('}', start);
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\fn}"))
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\fn}", StringComparison.Ordinal))
|
||||
{
|
||||
string fontName = text.Substring(start + 4, end - (start + 4));
|
||||
string extraTags = string.Empty;
|
||||
@@ -210,7 +213,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\fn}", start);
|
||||
int indexOfEndTag = text.IndexOf("{\\fn}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
|
||||
@@ -222,11 +225,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Contains(@"{\fs"))
|
||||
if (text.Contains(@"{\fs", StringComparison.Ordinal))
|
||||
{
|
||||
int start = text.IndexOf(@"{\fs");
|
||||
int start = text.IndexOf(@"{\fs", StringComparison.Ordinal);
|
||||
int end = text.IndexOf('}', start);
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\fs}"))
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\fs}", StringComparison.Ordinal))
|
||||
{
|
||||
string fontSize = text.Substring(start + 4, end - (start + 4));
|
||||
string extraTags = string.Empty;
|
||||
@@ -243,7 +246,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\fs}", start);
|
||||
int indexOfEndTag = text.IndexOf("{\\fs}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
|
||||
@@ -256,17 +259,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Contains(@"{\c"))
|
||||
if (text.Contains(@"{\c", StringComparison.Ordinal))
|
||||
{
|
||||
int start = text.IndexOf(@"{\c");
|
||||
int start = text.IndexOf(@"{\c", StringComparison.Ordinal);
|
||||
int end = text.IndexOf('}', start);
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\c}"))
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\c}", StringComparison.Ordinal))
|
||||
{
|
||||
string color = text.Substring(start + 4, end - (start + 4));
|
||||
string extraTags = string.Empty;
|
||||
CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
|
||||
|
||||
color = color.Replace("&", string.Empty).TrimStart('H');
|
||||
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
|
||||
color = color.PadLeft(6, '0');
|
||||
|
||||
// switch to rrggbb from bbggrr
|
||||
@@ -282,7 +285,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||
}
|
||||
int indexOfEndTag = text.IndexOf("{\\c}", start);
|
||||
|
||||
int indexOfEndTag = text.IndexOf("{\\c}", start, StringComparison.Ordinal);
|
||||
if (indexOfEndTag > 0)
|
||||
{
|
||||
text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
|
||||
@@ -294,17 +298,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
}
|
||||
|
||||
if (text.Contains(@"{\1c")) // "1" specifices primary color
|
||||
if (text.Contains(@"{\1c", StringComparison.Ordinal)) // "1" specifices primary color
|
||||
{
|
||||
int start = text.IndexOf(@"{\1c");
|
||||
int start = text.IndexOf(@"{\1c", StringComparison.Ordinal);
|
||||
int end = text.IndexOf('}', start);
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\1c}"))
|
||||
if (end > 0 && !text.Substring(start).StartsWith("{\\1c}", StringComparison.Ordinal))
|
||||
{
|
||||
string color = text.Substring(start + 5, end - (start + 5));
|
||||
string extraTags = string.Empty;
|
||||
CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
|
||||
|
||||
color = color.Replace("&", string.Empty).TrimStart('H');
|
||||
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
|
||||
color = color.PadLeft(6, '0');
|
||||
|
||||
// switch to rrggbb from bbggrr
|
||||
@@ -320,30 +324,31 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||
}
|
||||
|
||||
text += "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\i1}", "<i>");
|
||||
text = text.Replace(@"{\i0}", "</i>");
|
||||
text = text.Replace(@"{\i}", "</i>");
|
||||
text = text.Replace(@"{\i1}", "<i>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\i0}", "</i>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\i}", "</i>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
|
||||
{
|
||||
text += "</i>";
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\u1}", "<u>");
|
||||
text = text.Replace(@"{\u0}", "</u>");
|
||||
text = text.Replace(@"{\u}", "</u>");
|
||||
text = text.Replace(@"{\u1}", "<u>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\u0}", "</u>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\u}", "</u>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
|
||||
{
|
||||
text += "</u>";
|
||||
}
|
||||
|
||||
text = text.Replace(@"{\b1}", "<b>");
|
||||
text = text.Replace(@"{\b0}", "</b>");
|
||||
text = text.Replace(@"{\b}", "</b>");
|
||||
text = text.Replace(@"{\b1}", "<b>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\b0}", "</b>", StringComparison.Ordinal);
|
||||
text = text.Replace(@"{\b}", "</b>", StringComparison.Ordinal);
|
||||
if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
|
||||
{
|
||||
text += "</b>";
|
||||
@@ -358,7 +363,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
private static int CountTagInText(string text, string tag)
|
||||
{
|
||||
int count = 0;
|
||||
int index = text.IndexOf(tag);
|
||||
int index = text.IndexOf(tag, StringComparison.Ordinal);
|
||||
while (index >= 0)
|
||||
{
|
||||
count++;
|
||||
@@ -367,7 +372,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
return count;
|
||||
}
|
||||
|
||||
index = text.IndexOf(tag, index + 1);
|
||||
index = text.IndexOf(tag, index + 1, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
return count;
|
||||
@@ -376,7 +381,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
|
||||
{
|
||||
italic = false;
|
||||
int indexOfSPlit = tagName.IndexOf(@"\");
|
||||
int indexOfSPlit = tagName.IndexOf('\\', StringComparison.Ordinal);
|
||||
if (indexOfSPlit > 0)
|
||||
{
|
||||
string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
|
||||
@@ -384,9 +389,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (rest.StartsWith("fs") && rest.Length > 2)
|
||||
if (rest.StartsWith("fs", StringComparison.Ordinal) && rest.Length > 2)
|
||||
{
|
||||
indexOfSPlit = rest.IndexOf(@"\");
|
||||
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
|
||||
string fontSize = rest;
|
||||
if (indexOfSPlit > 0)
|
||||
{
|
||||
@@ -400,9 +405,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
extraTags += " size=\"" + fontSize.Substring(2) + "\"";
|
||||
}
|
||||
else if (rest.StartsWith("fn") && rest.Length > 2)
|
||||
else if (rest.StartsWith("fn", StringComparison.Ordinal) && rest.Length > 2)
|
||||
{
|
||||
indexOfSPlit = rest.IndexOf(@"\");
|
||||
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
|
||||
string fontName = rest;
|
||||
if (indexOfSPlit > 0)
|
||||
{
|
||||
@@ -416,9 +421,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
extraTags += " face=\"" + fontName.Substring(2) + "\"";
|
||||
}
|
||||
else if (rest.StartsWith("c") && rest.Length > 2)
|
||||
else if (rest.StartsWith("c", StringComparison.Ordinal) && rest.Length > 2)
|
||||
{
|
||||
indexOfSPlit = rest.IndexOf(@"\");
|
||||
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
|
||||
string fontColor = rest;
|
||||
if (indexOfSPlit > 0)
|
||||
{
|
||||
@@ -431,7 +436,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
|
||||
string color = fontColor.Substring(2);
|
||||
color = color.Replace("&", string.Empty).TrimStart('H');
|
||||
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
|
||||
color = color.PadLeft(6, '0');
|
||||
// switch to rrggbb from bbggrr
|
||||
color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
|
||||
@@ -439,9 +444,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
extraTags += " color=\"" + color + "\"";
|
||||
}
|
||||
else if (rest.StartsWith("i1") && rest.Length > 1)
|
||||
else if (rest.StartsWith("i1", StringComparison.Ordinal) && rest.Length > 1)
|
||||
{
|
||||
indexOfSPlit = rest.IndexOf(@"\");
|
||||
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
|
||||
italic = true;
|
||||
if (indexOfSPlit > 0)
|
||||
{
|
||||
@@ -452,9 +457,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
rest = string.Empty;
|
||||
}
|
||||
}
|
||||
else if (rest.Length > 0 && rest.Contains("\\"))
|
||||
else if (rest.Length > 0 && rest.Contains('\\', StringComparison.Ordinal))
|
||||
{
|
||||
indexOfSPlit = rest.IndexOf(@"\");
|
||||
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
|
||||
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
/// <summary>
|
||||
/// The _semaphoreLocks.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
|
||||
new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
public SubtitleEncoder(
|
||||
ILibraryManager libraryManager,
|
||||
ILogger<SubtitleEncoder> logger,
|
||||
@@ -269,25 +275,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
|
||||
}
|
||||
|
||||
private struct SubtitleInfo
|
||||
{
|
||||
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
|
||||
{
|
||||
Path = path;
|
||||
Protocol = protocol;
|
||||
Format = format;
|
||||
IsExternal = isExternal;
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public MediaProtocol Protocol { get; set; }
|
||||
|
||||
public string Format { get; set; }
|
||||
|
||||
public bool IsExternal { get; set; }
|
||||
}
|
||||
|
||||
private ISubtitleParser GetReader(string format, bool throwIfMissing)
|
||||
{
|
||||
if (string.IsNullOrEmpty(format))
|
||||
@@ -360,12 +347,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
throw new ArgumentException("Unsupported format: " + format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _semaphoreLocks.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
|
||||
new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lock.
|
||||
/// </summary>
|
||||
@@ -380,6 +361,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
/// Converts the text subtitle to SRT.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="inputProtocol">The input protocol.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
@@ -407,14 +389,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
/// Converts the text subtitle to SRT internal.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="inputProtocol">The input protocol.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// inputPath
|
||||
/// or
|
||||
/// outputPath
|
||||
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
|
||||
/// </exception>
|
||||
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -434,11 +415,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
// FFmpeg automatically convert character encoding when it is UTF-16
|
||||
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
|
||||
if ((inputPath.EndsWith(".smi") || inputPath.EndsWith(".sami")) &&
|
||||
if ((inputPath.EndsWith(".smi", StringComparison.Ordinal) || inputPath.EndsWith(".sami", StringComparison.Ordinal)) &&
|
||||
(encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
|
||||
encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
encodingParam = "";
|
||||
encodingParam = string.Empty;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(encodingParam))
|
||||
{
|
||||
@@ -454,7 +435,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||
Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
},
|
||||
@@ -525,7 +506,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle conversion failed for {0}", inputPath));
|
||||
}
|
||||
|
||||
await SetAssFont(outputPath).ConfigureAwait(false);
|
||||
await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath);
|
||||
}
|
||||
@@ -540,7 +521,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentException">Must use inputPath list overload</exception>
|
||||
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
|
||||
private async Task ExtractTextSubtitle(
|
||||
string[] inputFiles,
|
||||
MediaProtocol protocol,
|
||||
@@ -687,7 +668,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await SetAssFont(outputPath).ConfigureAwait(false);
|
||||
await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,8 +676,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
/// Sets the ass font.
|
||||
/// </summary>
|
||||
/// <param name="file">The file.</param>
|
||||
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <c>System.Threading.CancellationToken.None</c>.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task SetAssFont(string file)
|
||||
private async Task SetAssFont(string file, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Setting ass font within {File}", file);
|
||||
|
||||
@@ -711,14 +693,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var newText = text.Replace(",Arial,", ",Arial Unicode MS,");
|
||||
var newText = text.Replace(",Arial,", ",Arial Unicode MS,", StringComparison.Ordinal);
|
||||
|
||||
if (!string.Equals(text, newText))
|
||||
if (!string.Equals(text, newText, StringComparison.Ordinal))
|
||||
{
|
||||
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
using (var writer = new StreamWriter(fileStream, encoding))
|
||||
{
|
||||
writer.Write(newText);
|
||||
await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,11 +737,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
|
||||
|
||||
// UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding
|
||||
if ((path.EndsWith(".ass") || path.EndsWith(".ssa") || path.EndsWith(".srt"))
|
||||
if ((path.EndsWith(".ass", StringComparison.Ordinal) || path.EndsWith(".ssa", StringComparison.Ordinal) || path.EndsWith(".srt", StringComparison.Ordinal))
|
||||
&& (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
charset = "";
|
||||
charset = string.Empty;
|
||||
}
|
||||
|
||||
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
||||
@@ -790,5 +772,24 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
throw new ArgumentOutOfRangeException(nameof(protocol));
|
||||
}
|
||||
}
|
||||
|
||||
private struct SubtitleInfo
|
||||
{
|
||||
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
|
||||
{
|
||||
Path = path;
|
||||
Protocol = protocol;
|
||||
Format = format;
|
||||
IsExternal = isExternal;
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public MediaProtocol Protocol { get; set; }
|
||||
|
||||
public string Format { get; set; }
|
||||
|
||||
public bool IsExternal { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user