aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorferferga <ferferga.fer@gmail.com>2020-10-19 17:28:07 +0200
committerferferga <ferferga.fer@gmail.com>2020-10-19 17:28:07 +0200
commit9fd01fade6ac971ba72a2e7dd54e2295f23839bc (patch)
tree20a5ff4a1621e765fc93c0e53b149edb61ff3654 /MediaBrowser.MediaEncoding
parentba03ed65fe64b724b3e8b5b94b9cbe1075c61da2 (diff)
parent49ac4c4044b1777dc3a25544aead7b0b15b953e8 (diff)
Remove "download images in advance" option
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs9
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs19
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs8
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs34
-rw-r--r--MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs38
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs214
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs16
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs130
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj16
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs11
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaChapter.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs167
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs43
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs216
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs140
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs9
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs2
23 files changed, 743 insertions, 399 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index 3f177a9fa..21b5d0c5b 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -21,7 +23,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
{
public class AttachmentExtractor : IAttachmentExtractor, IDisposable
{
- private readonly ILogger _logger;
+ private readonly ILogger<AttachmentExtractor> _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
@@ -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);
@@ -269,7 +271,6 @@ namespace MediaBrowser.MediaEncoding.Attachments
if (disposing)
{
-
}
_disposed = true;
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
index e040286ab..4a54b677d 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -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));
}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index 3260f3051..9108d9649 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -9,12 +9,16 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.BdInfo
{
/// <summary>
- /// Class BdInfoExaminer
+ /// Class BdInfoExaminer.
/// </summary>
public class BdInfoExaminer : IBlurayExaminer
{
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;
@@ -41,7 +45,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
var outputStream = new BlurayDiscInfo
{
- MediaStreams = new MediaStream[] { }
+ MediaStreams = Array.Empty<MediaStream>()
};
if (playlist == null)
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
index a6ff4f767..0a8af8e9c 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
@@ -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);
diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
index 75534b5bd..f81a337db 100644
--- a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
+++ b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
@@ -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));
- }
- }
- }
- }
}
diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs
new file mode 100644
index 000000000..2f158157e
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs
@@ -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));
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 6e036d24c..3287f9814 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -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,28 +14,50 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
private const string DefaultEncoderPath = "ffmpeg";
- private static readonly string[] requiredDecoders = new[]
+ private static readonly string[] _requiredDecoders = new[]
{
+ "h264",
+ "hevc",
"mpeg2video",
+ "mpeg4",
+ "msmpeg4",
+ "dts",
+ "ac3",
+ "aac",
+ "mp3",
"h264_qsv",
"hevc_qsv",
"mpeg2_qsv",
- "mpeg2_mmal",
- "mpeg4_mmal",
"vc1_qsv",
- "vc1_mmal",
+ "vp8_qsv",
+ "vp9_qsv",
"h264_cuvid",
"hevc_cuvid",
- "dts",
- "ac3",
- "aac",
- "mp3",
- "h264",
+ "mpeg2_cuvid",
+ "vc1_cuvid",
+ "mpeg4_cuvid",
+ "vp8_cuvid",
+ "vp9_cuvid",
"h264_mmal",
- "hevc"
+ "mpeg2_mmal",
+ "mpeg4_mmal",
+ "vc1_mmal",
+ "h264_mediacodec",
+ "hevc_mediacodec",
+ "mpeg2_mediacodec",
+ "mpeg4_mediacodec",
+ "vp8_mediacodec",
+ "vp9_mediacodec",
+ "h264_opencl",
+ "hevc_opencl",
+ "mpeg2_opencl",
+ "mpeg4_opencl",
+ "vp8_opencl",
+ "vp9_opencl",
+ "vc1_opencl"
};
- private static readonly string[] requiredEncoders = new[]
+ private static readonly string[] _requiredEncoders = new[]
{
"libx264",
"libx265",
@@ -43,36 +67,37 @@ namespace MediaBrowser.MediaEncoding.Encoder
"libvpx-vp9",
"aac",
"libfdk_aac",
+ "ac3",
"libmp3lame",
"libopus",
"libvorbis",
"srt",
- "h264_nvenc",
- "hevc_nvenc",
+ "h264_amf",
+ "hevc_amf",
"h264_qsv",
"hevc_qsv",
- "h264_omx",
- "hevc_omx",
+ "h264_nvenc",
+ "hevc_nvenc",
"h264_vaapi",
"hevc_vaapi",
+ "h264_omx",
+ "hevc_omx",
"h264_v4l2m2m",
- "ac3",
- "h264_amf",
- "hevc_amf"
+ "h264_videotoolbox",
+ "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.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;
@@ -85,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;
@@ -123,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;
}
@@ -159,63 +195,103 @@ namespace MediaBrowser.MediaEncoding.Encoder
public IEnumerable<string> GetEncoders() => GetCodecs(Codec.Encoder);
+ public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
+
/// <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?((?:\d+\.?)+)");
+ var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
if (match.Success)
{
- return new Version(match.Groups[1].Value);
+ if (Version.TryParse(match.Groups[1].Value, out var result))
+ {
+ return result;
+ }
}
- 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>\d+)\.\s*(?<minor>\d+))",
+ @"((?<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();
+ return map;
}
- private enum Codec
+ private IEnumerable<string> GetHwaccelTypes()
{
- Encoder,
- Decoder
+ string output = null;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-hwaccels");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting available hwaccel types");
+ }
+
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ return Enumerable.Empty<string>();
+ }
+
+ var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
+ _logger.LogInformation("Available hwaccel types: {Types}", found);
+
+ return found;
}
private IEnumerable<string> GetCodecs(Codec codec)
@@ -236,7 +312,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)
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index d4aede572..63310fdf6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -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);
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 1377502dd..5a3a9185d 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -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,13 +23,13 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
-using System.Diagnostics;
namespace MediaBrowser.MediaEncoding.Encoder
{
/// <summary>
- /// Class MediaEncoder
+ /// Class MediaEncoder.
/// </summary>
public class MediaEncoder : IMediaEncoder, IDisposable
{
@@ -34,7 +38,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
internal const int DefaultImageExtractionTimeout = 5000;
- private readonly ILogger _logger;
+ /// <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;
private readonly ILocalizationManager _localization;
@@ -46,7 +55,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
- private string _ffmpegPath;
+ // 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;
public MediaEncoder(
@@ -55,14 +71,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
IFileSystem fileSystem,
ILocalizationManager localization,
Lazy<EncodingHelper> encodingHelperFactory,
- string startupOptionsFFmpegPath)
+ IConfiguration config)
{
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
_encodingHelperFactory = encodingHelperFactory;
- _startupOptionFFmpegPath = startupOptionsFFmpegPath;
+ _startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
+ _jsonSerializerOptions = JsonDefaults.GetOptions();
}
private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
@@ -76,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()
{
@@ -111,6 +128,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
+ SetAvailableHwaccels(validator.GetHwaccels());
}
_logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
@@ -120,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;
@@ -165,9 +183,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// 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>
+ /// <param name="path">FQPN to test.</param>
+ /// <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;
@@ -183,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;
}
@@ -219,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);
@@ -228,6 +243,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
return inJellyfinPath;
}
+
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(Path.PathSeparator))
@@ -243,18 +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()));
+ }
+
+ public void SetAvailableHwaccels(IEnumerable<string> list)
+ {
+ _hwaccels = list.ToList();
}
public bool SupportsEncoder(string encoder)
@@ -267,6 +284,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
}
+ public bool SupportsHwaccel(string hwaccel)
+ {
+ return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -317,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>
@@ -327,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);
@@ -335,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,
@@ -348,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
{
@@ -363,7 +394,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
FileName = _ffprobePath,
Arguments = args,
-
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
},
@@ -389,6 +419,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
process.StandardOutput.BaseStream,
+ _jsonSerializerOptions,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch
@@ -424,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);
@@ -444,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);
@@ -500,11 +534,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
break;
case Video3DFormat.FullSideBySide:
vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
+ // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
break;
case Video3DFormat.HalfTopAndBottom:
vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
+ // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
break;
case Video3DFormat.FullTopAndBottom:
vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
@@ -521,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);
@@ -539,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)
@@ -610,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);
@@ -630,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(
@@ -647,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);
@@ -759,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);
@@ -825,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 />
@@ -844,6 +878,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (dispose)
{
StopProcesses();
+ _thumbnailResourcePool.Dispose();
}
}
@@ -920,7 +955,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_');
return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
-
}).ToList();
// If this resulted in not getting any vobs, just take them all
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index af8bee301..6ead93e09 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -9,6 +9,7 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@@ -23,8 +24,21 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
- <PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
+ <PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<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>
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index 78dc7b607..b2d4db894 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -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>
@@ -35,7 +38,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a string from an FFProbeResult tags dictionary
+ /// Gets a string from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -52,7 +55,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets an int from an FFProbeResult tags dictionary
+ /// Gets an int from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -73,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a DateTime from an FFProbeResult tags dictionary
+ /// Gets a DateTime from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -94,7 +97,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts a dictionary to case insensitive
+ /// Converts a dictionary to case insensitive.
/// </summary>
/// <param name="dict">The dict.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
index 6a45ccf49..de062d06b 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index a2ea0766a..8a7c032c5 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -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; }
@@ -280,6 +283,20 @@ namespace MediaBrowser.MediaEncoding.Probing
public IReadOnlyDictionary<string, int> Disposition { get; set; }
/// <summary>
+ /// Gets or sets the color range.
+ /// </summary>
+ /// <value>The color range.</value>
+ [JsonPropertyName("color_range")]
+ public string ColorRange { get; set; }
+
+ /// <summary>
+ /// Gets or sets the color space.
+ /// </summary>
+ /// <value>The color space.</value>
+ [JsonPropertyName("color_space")]
+ public string ColorSpace { get; set; }
+
+ /// <summary>
/// Gets or sets the color transfer.
/// </summary>
/// <value>The color transfer.</value>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index d3f8094b9..22537a4d9 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -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);
@@ -93,6 +105,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
}
+
if (string.IsNullOrWhiteSpace(overview))
{
overview = FFProbeHelpers.GetDictionaryValue(tags, "desc");
@@ -274,10 +287,12 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
ReadFromDictNode(subtree, info);
}
+
break;
default:
reader.Skip();
@@ -319,6 +334,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
ProcessPairs(currentKey, pairs, info);
}
+
currentKey = reader.ReadElementContentAsString();
pairs = new List<NameValuePair>();
break;
@@ -332,6 +348,7 @@ namespace MediaBrowser.MediaEncoding.Probing
Value = value
});
}
+
break;
case "array":
if (reader.IsEmptyElement)
@@ -339,6 +356,7 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
if (!string.IsNullOrWhiteSpace(currentKey))
@@ -346,6 +364,7 @@ namespace MediaBrowser.MediaEncoding.Probing
pairs.AddRange(ReadValueArray(subtree));
}
}
+
break;
default:
reader.Skip();
@@ -361,7 +380,6 @@ namespace MediaBrowser.MediaEncoding.Probing
private List<NameValuePair> ReadValueArray(XmlReader reader)
{
-
var pairs = new List<NameValuePair>();
reader.MoveToContent();
@@ -381,6 +399,7 @@ namespace MediaBrowser.MediaEncoding.Probing
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
var dict = GetNameValuePair(subtree);
@@ -389,6 +408,7 @@ namespace MediaBrowser.MediaEncoding.Probing
pairs.Add(dict);
}
}
+
break;
default:
reader.Skip();
@@ -413,7 +433,6 @@ namespace MediaBrowser.MediaEncoding.Probing
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
-
}
else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase))
{
@@ -425,7 +444,6 @@ namespace MediaBrowser.MediaEncoding.Probing
Type = PersonType.Writer
});
}
-
}
else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase))
{
@@ -517,7 +535,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts ffprobe stream info to our MediaAttachment class
+ /// Converts ffprobe stream info to our MediaAttachment class.
/// </summary>
/// <param name="streamInfo">The stream info.</param>
/// <returns>MediaAttachments.</returns>
@@ -550,7 +568,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Converts ffprobe stream info to our MediaStream class
+ /// Converts ffprobe stream info to our MediaStream class.
/// </summary>
/// <param name="isAudio">if set to <c>true</c> [is info].</param>
/// <param name="streamInfo">The stream info.</param>
@@ -562,7 +580,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase))
{
// Edit: but these are also sometimes subtitles?
- //return null;
+ // return null;
}
var stream = new MediaStream
@@ -684,7 +702,7 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.BitDepth = streamInfo.BitsPerRawSample;
}
- //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
+ // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
@@ -696,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.RefFrames = streamInfo.Refs;
}
+ if (!string.IsNullOrEmpty(streamInfo.ColorRange))
+ {
+ stream.ColorRange = streamInfo.ColorRange;
+ }
+
+ if (!string.IsNullOrEmpty(streamInfo.ColorSpace))
+ {
+ stream.ColorSpace = streamInfo.ColorSpace;
+ }
+
if (!string.IsNullOrEmpty(streamInfo.ColorTransfer))
{
stream.ColorTransfer = streamInfo.ColorTransfer;
@@ -769,7 +797,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a string from an FFProbeResult tags dictionary
+ /// Gets a string from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
@@ -942,48 +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");
@@ -999,7 +1025,7 @@ namespace MediaBrowser.MediaEncoding.Probing
var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
- audio.Artists = new string[] { };
+ audio.Artists = Array.Empty<string>();
}
else
{
@@ -1014,6 +1040,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
}
+
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
@@ -1021,14 +1048,13 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.IsNullOrWhiteSpace(albumArtist))
{
- audio.AlbumArtists = new string[] { };
+ audio.AlbumArtists = Array.Empty<string>();
}
else
{
audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true)
.DistinctNames()
.ToArray();
-
}
if (audio.AlbumArtists.Length == 0)
@@ -1056,24 +1082,44 @@ namespace MediaBrowser.MediaEncoding.Probing
// These support mulitple values, but for now we only store the first.
var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID"));
- audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mb);
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID"));
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID"));
- audio.SetProviderId(MetadataProviders.MusicBrainzArtist, mb);
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID"));
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID"));
- audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, mb);
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID"));
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID"));
- audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mb);
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID"));
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb);
mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id"));
- if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID"));
- audio.SetProviderId(MetadataProviders.MusicBrainzTrack, mb);
+ if (mb == null)
+ {
+ mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID"));
+ }
+
+ audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb);
}
private string GetMultipleMusicBrainzId(string value)
@@ -1088,8 +1134,6 @@ namespace MediaBrowser.MediaEncoding.Probing
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
- private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
-
/// <summary>
/// Splits the specified val.
/// </summary>
@@ -1100,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[] { ',' };
@@ -1109,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)
@@ -1140,9 +1182,6 @@ namespace MediaBrowser.MediaEncoding.Probing
return artistsFound;
}
-
- private List<string> _splitWhiteList = null;
-
private IEnumerable<string> GetSplitWhitelist()
{
if (_splitWhiteList == null)
@@ -1157,7 +1196,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets the studios from the tags collection
+ /// Gets the studios from the tags collection.
/// </summary>
/// <param name="info">The info.</param>
/// <param name="tags">The tags.</param>
@@ -1178,6 +1217,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
continue;
}
+
if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
{
continue;
@@ -1194,7 +1234,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets the genres from the tags collection
+ /// Gets the genres from the tags collection.
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
@@ -1218,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>
@@ -1264,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)
@@ -1337,7 +1375,9 @@ namespace MediaBrowser.MediaEncoding.Probing
// OR -> COMMENT. SUBTITLE: DESCRIPTION
// e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
// e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
- if (string.IsNullOrWhiteSpace(subTitle) && !string.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
+ if (string.IsNullOrWhiteSpace(subTitle)
+ && !string.IsNullOrWhiteSpace(description)
+ && description.AsSpan().Slice(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).IndexOf(':') != -1) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
{
string[] parts = description.Split(':');
if (parts.Length > 0)
@@ -1345,23 +1385,30 @@ namespace MediaBrowser.MediaEncoding.Probing
string subtitle = parts[0];
try
{
- if (subtitle.Contains("/")) // It contains a episode number and season number
+ 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
}
else
+ {
throw new Exception(); // Switch to default parsing
+ }
}
catch // Default parsing
{
- if (subtitle.Contains(".")) // skip the comment, keep the subtitle
+ if (subtitle.Contains('.', StringComparison.Ordinal))
+ {
+ // skip the comment, keep the subtitle
description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
+ }
else
+ {
description = subtitle.Trim(); // Clean up whitespaces and save it
+ }
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index 293cf5ea5..86b87fddd 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -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,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
string line;
while (reader.ReadLine() != "[Events]")
- { }
+ {
+ }
+
var headers = ParseFieldHeaders(reader.ReadLine());
while ((line = reader.ReadLine()) != null)
@@ -33,10 +38,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
continue;
}
- if (line.StartsWith("["))
+
+ if (line[0] == '[')
+ {
break;
- if (string.IsNullOrEmpty(line))
- continue;
+ }
+
var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
eventIndex++;
var sections = line.Substring(10).Split(',');
@@ -54,11 +61,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackEvents.Add(subEvent);
}
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
- long GetTicks(string time)
+ private long GetTicks(ReadOnlySpan<char> time)
{
return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out var span)
? span.Ticks : 0;
@@ -68,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) ||
@@ -110,11 +115,13 @@ 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;
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
index f0d107196..c0023ebf2 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs
index bf8808eb8..dca5c1e8a 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs
@@ -1,6 +1,9 @@
+#nullable enable
+#pragma warning disable CS1591
+
namespace MediaBrowser.MediaEncoding.Subtitles
{
- public class ParserValues
+ public static class ParserValues
{
public const string NewLine = "\r\n";
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
index c98dd1502..cc35efb3f 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -20,6 +22,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger = logger;
}
+ /// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var trackInfo = new SubtitleTrackInfo();
@@ -35,6 +38,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
continue;
}
+
var subEvent = new SubtitleTrackEvent { Id = line };
line = reader.ReadLine();
@@ -52,11 +56,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.LogWarning("Unrecognized line in srt: {0}", line);
continue;
}
+
subEvent.StartPositionTicks = GetTicks(time[0]);
- var endTime = time[1];
- var idx = endTime.IndexOf(" ", StringComparison.Ordinal);
+ var endTime = time[1].AsSpan();
+ var idx = endTime.IndexOf(' ');
if (idx > 0)
- endTime = endTime.Substring(0, idx);
+ {
+ endTime = endTime.Slice(0, idx);
+ }
+
subEvent.EndPositionTicks = GetTicks(endTime);
var multiline = new List<string>();
while ((line = reader.ReadLine()) != null)
@@ -65,8 +73,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
break;
}
+
multiline.Add(line);
}
+
subEvent.Text = string.Join(ParserValues.NewLine, multiline);
subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
@@ -76,11 +86,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackEvents.Add(subEvent);
}
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
- long GetTicks(string time)
+ private long GetTicks(ReadOnlySpan<char> time)
{
return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out var span)
? span.Ticks
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs
index 45b317b2e..143c010b7 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs
@@ -8,8 +8,12 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
+ /// <summary>
+ /// SRT subtitle writer.
+ /// </summary>
public class SrtWriter : ISubtitleWriter
{
+ /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index b94d45165..a5d641747 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
@@ -8,10 +9,11 @@ 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
{
+ /// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var trackInfo = new SubtitleTrackInfo();
@@ -41,38 +43,52 @@ namespace MediaBrowser.MediaEncoding.Subtitles
lineNumber++;
if (!eventsStarted)
+ {
header.AppendLine(line);
+ }
- if (line.Trim().ToLowerInvariant() == "[events]")
+ if (string.Equals(line.Trim(), "[events]", StringComparison.OrdinalIgnoreCase))
{
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)
{
format = line.ToLowerInvariant().Substring(8).Split(',');
for (int i = 0; i < format.Length; i++)
{
- if (format[i].Trim().ToLowerInvariant() == "layer")
+ if (string.Equals(format[i].Trim(), "layer", StringComparison.OrdinalIgnoreCase))
+ {
indexLayer = i;
- else if (format[i].Trim().ToLowerInvariant() == "start")
+ }
+ else if (string.Equals(format[i].Trim(), "start", StringComparison.OrdinalIgnoreCase))
+ {
indexStart = i;
- else if (format[i].Trim().ToLowerInvariant() == "end")
+ }
+ else if (string.Equals(format[i].Trim(), "end", StringComparison.OrdinalIgnoreCase))
+ {
indexEnd = i;
- else if (format[i].Trim().ToLowerInvariant() == "text")
+ }
+ else if (string.Equals(format[i].Trim(), "text", StringComparison.OrdinalIgnoreCase))
+ {
indexText = i;
- else if (format[i].Trim().ToLowerInvariant() == "effect")
+ }
+ else if (string.Equals(format[i].Trim(), "effect", StringComparison.OrdinalIgnoreCase))
+ {
indexEffect = i;
- else if (format[i].Trim().ToLowerInvariant() == "style")
+ }
+ else if (string.Equals(format[i].Trim(), "style", StringComparison.OrdinalIgnoreCase))
+ {
indexStyle = i;
+ }
}
}
}
@@ -88,29 +104,49 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string[] splittedLine;
- if (s.StartsWith("dialogue:"))
+ if (s.StartsWith("dialogue:", StringComparison.Ordinal))
+ {
splittedLine = line.Substring(10).Split(',');
+ }
else
+ {
splittedLine = line.Split(',');
+ }
for (int i = 0; i < splittedLine.Length; i++)
{
if (i == indexStart)
+ {
start = splittedLine[i].Trim();
+ }
else if (i == indexEnd)
+ {
end = splittedLine[i].Trim();
+ }
else if (i == indexLayer)
+ {
layer = splittedLine[i];
+ }
else if (i == indexEffect)
+ {
effect = splittedLine[i];
+ }
else if (i == indexText)
+ {
text = splittedLine[i];
+ }
else if (i == indexStyle)
+ {
style = splittedLine[i];
+ }
else if (i == indexName)
+ {
name = splittedLine[i];
+ }
else if (i > indexText)
+ {
text += "," + splittedLine[i];
+ }
}
try
@@ -130,11 +166,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- //if (header.Length > 0)
- //subtitle.Header = header.ToString();
+ // if (header.Length > 0)
+ // subtitle.Header = header.ToString();
- //subtitle.Renumber(1);
+ // subtitle.Renumber(1);
}
+
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
@@ -143,80 +180,96 @@ 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;
}
- public static string GetFormattedText(string text)
+ private static string GetFormattedText(string text)
{
text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
- bool italic = false;
-
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;
- CheckAndAddSubTags(ref fontName, ref extraTags, out italic);
+ CheckAndAddSubTags(ref fontName, ref extraTags, out bool italic);
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
+ }
else
+ {
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>");
+ }
else
+ {
text += "</font>";
+ }
}
}
- 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;
- CheckAndAddSubTags(ref fontSize, ref extraTags, out italic);
+ CheckAndAddSubTags(ref fontSize, ref extraTags, out bool italic);
if (IsInteger(fontSize))
{
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
+ }
else
+ {
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>");
+ }
else
+ {
text += "</font>";
+ }
}
}
}
- 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 italic);
+ 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
@@ -225,28 +278,37 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ }
else
+ {
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>");
+ }
else
+ {
text += "</font>";
+ }
}
}
- 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 italic);
+ 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
@@ -255,61 +317,71 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = text.Remove(start, end - start + 1);
if (italic)
+ {
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
+ }
else
+ {
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>";
+ }
return text;
}
private static bool IsInteger(string s)
- {
- if (int.TryParse(s, out var i))
- return true;
- return false;
- }
+ => int.TryParse(s, out _);
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++;
if (index == text.Length)
+ {
return count;
- index = text.IndexOf(tag, index + 1);
+ }
+
+ index = text.IndexOf(tag, index + 1, StringComparison.Ordinal);
}
+
return count;
}
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('\\');
@@ -317,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)
{
@@ -330,11 +402,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
rest = string.Empty;
}
+
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)
{
@@ -345,11 +418,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
rest = string.Empty;
}
+
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)
{
@@ -362,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);
@@ -370,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)
{
@@ -383,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('\\');
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index ba171295e..0a9958b9e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -1,9 +1,12 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -25,20 +28,26 @@ namespace MediaBrowser.MediaEncoding.Subtitles
public class SubtitleEncoder : ISubtitleEncoder
{
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
+ private readonly ILogger<SubtitleEncoder> _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
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,
IApplicationPaths appPaths,
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
@@ -46,7 +55,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_appPaths = appPaths;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager;
}
@@ -115,6 +124,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
throw new ArgumentNullException(nameof(item));
}
+
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
throw new ArgumentNullException(nameof(mediaSourceId));
@@ -171,7 +181,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
inputFiles = new[] { mediaSource.Path };
}
- var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
+ var protocol = mediaSource.Protocol;
+ if (subtitleStream.IsExternal)
+ {
+ protocol = _mediaSourceManager.GetPathProtocol(subtitleStream.Path);
+ }
+
+ var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
@@ -260,22 +276,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))
@@ -287,10 +287,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new SrtParser(_logger);
}
+
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
return new SsaParser();
}
+
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
return new AssParser();
@@ -315,14 +317,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
return new JsonWriter();
}
+
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
return new SrtWriter();
}
+
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
{
return new VttWriter();
}
+
if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase))
{
return new TtmlWriter();
@@ -344,25 +349,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
/// <summary>
- /// The _semaphoreLocks
- /// </summary>
- private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
- new ConcurrentDictionary<string, SemaphoreSlim>();
-
- /// <summary>
/// Gets the lock.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.Object.</returns>
private SemaphoreSlim GetLock(string filename)
{
- return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
+ return _semaphoreLocks.GetOrAdd(filename, _ => new SemaphoreSlim(1, 1));
}
/// <summary>
/// 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>
@@ -390,14 +390,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)
{
@@ -417,9 +416,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")) && (encodingParam == "UTF-16BE" || encodingParam == "UTF-16LE"))
+ 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))
{
@@ -435,7 +436,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
},
@@ -506,7 +507,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);
}
@@ -521,7 +522,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,
@@ -640,7 +641,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
catch (FileNotFoundException)
{
-
}
catch (IOException ex)
{
@@ -669,7 +669,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase))
{
- await SetAssFont(outputPath).ConfigureAwait(false);
+ await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false);
}
}
@@ -677,8 +677,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);
@@ -693,14 +694,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);
}
}
}
@@ -713,19 +714,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
- var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
- var prefix = filename.Substring(0, 1);
+ var prefix = filename.Slice(0, 1);
- return Path.Combine(SubtitleCachePath, prefix, filename);
+ return Path.Join(SubtitleCachePath, prefix, filename);
}
else
{
- var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
+ ReadOnlySpan<char> filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
- var prefix = filename.Substring(0, 1);
+ var prefix = filename.Slice(0, 1);
- return Path.Combine(SubtitleCachePath, prefix, filename);
+ return Path.Join(SubtitleCachePath, prefix, filename);
}
}
@@ -737,11 +738,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"))
+ 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);
@@ -750,27 +751,42 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+ private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
switch (protocol)
{
case MediaProtocol.Http:
- var opts = new HttpRequestOptions()
- {
- Url = path,
- CancellationToken = cancellationToken,
-
- // Needed for seeking
- BufferContent = true
- };
-
- return _httpClient.Get(opts);
+ {
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetAsync(path, cancellationToken)
+ .ConfigureAwait(false);
+ return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ }
case MediaProtocol.File:
- return Task.FromResult<Stream>(File.OpenRead(path));
+ return File.OpenRead(path);
default:
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; }
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
index 7d3e18578..e5c785bc5 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
@@ -6,8 +6,12 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
+ /// <summary>
+ /// TTML subtitle writer.
+ /// </summary>
public class TtmlWriter : ISubtitleWriter
{
+ /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
// Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml
@@ -36,9 +40,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase);
- writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>",
+ writer.WriteLine(
+ "<p begin=\"{0}\" dur=\"{1}\">{2}</p>",
trackEvent.StartPositionTicks,
- (trackEvent.EndPositionTicks - trackEvent.StartPositionTicks),
+ trackEvent.EndPositionTicks - trackEvent.StartPositionTicks,
text);
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
index de35acbba..ad32cb794 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
writer.WriteLine(text);
- writer.WriteLine(string.Empty);
+ writer.WriteLine();
}
}
}