aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs1
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs6
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs16
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs119
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs164
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj5
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs45
-rw-r--r--MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaChapter.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs699
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs10
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs66
19 files changed, 581 insertions, 569 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index e8aeabf9d..a0ec3bd90 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
index ef9943722..e86e518be 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -9,9 +9,9 @@ namespace MediaBrowser.MediaEncoding.BdInfo
{
public class BdInfoDirectoryInfo : IDirectoryInfo
{
- private readonly IFileSystem _fileSystem = null;
+ private readonly IFileSystem _fileSystem;
- private readonly FileSystemMetadata _impl = null;
+ private readonly FileSystemMetadata _impl;
public BdInfoDirectoryInfo(IFileSystem fileSystem, string path)
{
@@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
public string FullName => _impl.FullName;
- public IDirectoryInfo Parent
+ public IDirectoryInfo? Parent
{
get
{
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index 9108d9649..6ebaa4fff 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -61,33 +61,25 @@ namespace MediaBrowser.MediaEncoding.BdInfo
foreach (var stream in playlist.SortedStreams)
{
- var videoStream = stream as TSVideoStream;
-
- if (videoStream != null)
+ if (stream is TSVideoStream videoStream)
{
AddVideoStream(mediaStreams, videoStream);
continue;
}
- var audioStream = stream as TSAudioStream;
-
- if (audioStream != null)
+ if (stream is TSAudioStream audioStream)
{
AddAudioStream(mediaStreams, audioStream);
continue;
}
- var textStream = stream as TSTextStream;
-
- if (textStream != null)
+ if (stream is TSTextStream textStream)
{
AddSubtitleStream(mediaStreams, textStream);
continue;
}
- var graphicsStream = stream as TSGraphicsStream;
-
- if (graphicsStream != null)
+ if (stream is TSGraphicsStream graphicsStream)
{
AddSubtitleStream(mediaStreams, graphicsStream);
}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
index 0a8af8e9c..41143c259 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
{
public class BdInfoFileInfo : BDInfo.IO.IFileInfo
{
- private FileSystemMetadata _impl = null;
+ private FileSystemMetadata _impl;
public BdInfoFileInfo(FileSystemMetadata impl)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 9e2417603..60a2d39e5 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
public class EncoderValidator
{
- private const string DefaultEncoderPath = "ffmpeg";
-
private static readonly string[] _requiredDecoders = new[]
{
"h264",
@@ -89,6 +87,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
"hevc_videotoolbox"
};
+ private static readonly string[] _requiredFilters = new[]
+ {
+ "scale_cuda",
+ "yadif_cuda",
+ "hwupload_cuda",
+ "overlay_cuda",
+ "tonemap_cuda",
+ "tonemap_opencl",
+ "tonemap_vaapi",
+ };
+
+ private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
+ {
+ { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
+ { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
+ { 2, new string[] { "tonemap_opencl", "bt2390" } }
+ };
+
// 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>
{
@@ -106,7 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly string _encoderPath;
- public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath)
+ public EncoderValidator(ILogger logger, string encoderPath)
{
_logger = logger;
_encoderPath = encoderPath;
@@ -121,11 +137,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
// 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;
+ public static Version? MaxVersion { get; } = null;
public bool ValidateVersion()
{
- string output = null;
+ string output;
try
{
output = GetProcessOutput(_encoderPath, "-version");
@@ -133,6 +149,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
catch (Exception ex)
{
_logger.LogError(ex, "Error validating encoder");
+ return false;
}
if (string.IsNullOrWhiteSpace(output))
@@ -155,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// Work out what the version under test is
- var version = GetFFmpegVersion(versionOutput);
+ var version = GetFFmpegVersionInternal(versionOutput);
_logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
@@ -199,6 +216,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
+ public IEnumerable<string> GetFilters() => GetFFmpegFilters();
+
+ public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
+
+ public Version? GetFFmpegVersion()
+ {
+ string output;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-version");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating encoder");
+ return null;
+ }
+
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ _logger.LogError("FFmpeg validation: The process returned no result");
+ return null;
+ }
+
+ _logger.LogDebug("ffmpeg output: {Output}", output);
+
+ return GetFFmpegVersionInternal(output);
+ }
+
/// <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
@@ -207,7 +252,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="output">The output from "ffmpeg -version".</param>
/// <returns>The FFmpeg version.</returns>
- internal Version GetFFmpegVersion(string output)
+ internal Version? GetFFmpegVersionInternal(string output)
{
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
@@ -275,7 +320,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private IEnumerable<string> GetHwaccelTypes()
{
- string output = null;
+ string? output = null;
try
{
output = GetProcessOutput(_encoderPath, "-hwaccels");
@@ -296,14 +341,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return found;
}
- public bool CheckFilter(string filter, string option)
+ public bool CheckFilterWithOption(string filter, string option)
{
- if (string.IsNullOrEmpty(filter))
+ if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
{
return false;
}
- string output = null;
+ string output;
try
{
output = GetProcessOutput(_encoderPath, "-h filter=" + filter);
@@ -311,15 +356,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
catch (Exception ex)
{
_logger.LogError(ex, "Error detecting the given filter");
+ return false;
}
if (output.Contains("Filter " + filter, StringComparison.Ordinal))
{
- if (string.IsNullOrEmpty(option))
- {
- return true;
- }
-
return output.Contains(option, StringComparison.Ordinal);
}
@@ -331,7 +372,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private IEnumerable<string> GetCodecs(Codec codec)
{
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
- string output = null;
+ string output;
try
{
output = GetProcessOutput(_encoderPath, "-" + codecstr);
@@ -339,6 +380,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
catch (Exception ex)
{
_logger.LogError(ex, "Error detecting available {Codec}", codecstr);
+ return Enumerable.Empty<string>();
}
if (string.IsNullOrWhiteSpace(output))
@@ -359,6 +401,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
return found;
}
+ private IEnumerable<string> GetFFmpegFilters()
+ {
+ string output;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-filters");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting available filters");
+ return Enumerable.Empty<string>();
+ }
+
+ if (string.IsNullOrWhiteSpace(output))
+ {
+ return Enumerable.Empty<string>();
+ }
+
+ var found = Regex
+ .Matches(output, @"^\s\S{3}\s(?<filter>[\w|-]+)\s+.+$", RegexOptions.Multiline)
+ .Cast<Match>()
+ .Select(x => x.Groups["filter"].Value)
+ .Where(x => _requiredFilters.Contains(x));
+
+ _logger.LogInformation("Available filters: {Filters}", found);
+
+ return found;
+ }
+
+ private IDictionary<int, bool> GetFFmpegFiltersWithOption()
+ {
+ IDictionary<int, bool> dict = new Dictionary<int, bool>();
+ for (int i = 0; i < _filterOptionsDict.Count; i++)
+ {
+ if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2)
+ {
+ dict.Add(i, CheckFilterWithOption(val[0], val[1]));
+ }
+ }
+
+ return dict;
+ }
+
private string GetProcessOutput(string path, string arguments)
{
using (var process = new Process()
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 62c0c0bb1..a7bcaf544 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
@@ -10,9 +11,9 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
@@ -22,7 +23,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@@ -65,10 +65,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _encoders = new List<string>();
private List<string> _decoders = new List<string>();
private List<string> _hwaccels = new List<string>();
+ private List<string> _filters = new List<string>();
+ private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
+ private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty;
private string _ffprobePath;
- private int threads;
+ private int _threads;
public MediaEncoder(
ILogger<MediaEncoder> logger,
@@ -88,9 +91,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
- /// <inheritdoc />
- public FFmpegLocation EncoderLocation { get; private set; }
-
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@@ -99,20 +99,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
- if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
+ var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath;
+ if (string.IsNullOrEmpty(ffmpegPath))
{
// 2) Check if the --ffmpeg CLI switch has been given
- if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
+ ffmpegPath = _startupOptionFFmpegPath;
+ if (string.IsNullOrEmpty(ffmpegPath))
{
- // 3) Search system $PATH environment variable for valid FFmpeg
- if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
- {
- EncoderLocation = FFmpegLocation.NotFound;
- _ffmpegPath = null;
- }
+ // 3) Check "ffmpeg"
+ ffmpegPath = "ffmpeg";
}
}
+ if (!ValidatePath(ffmpegPath))
+ {
+ _ffmpegPath = null;
+ }
+
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
@@ -129,11 +132,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
+ SetAvailableFilters(validator.GetFilters());
+ SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
SetAvailableHwaccels(validator.GetHwaccels());
- threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
+ SetMediaEncoderVersion(validator);
+
+ _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
}
- _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
+ _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
}
/// <summary>
@@ -152,15 +159,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
throw new ArgumentException("Unexpected pathType value");
}
- else if (string.IsNullOrWhiteSpace(path))
+
+ if (string.IsNullOrWhiteSpace(path))
{
// User had cleared the custom path in UI
newPath = string.Empty;
}
- else if (File.Exists(path))
- {
- newPath = path;
- }
else if (Directory.Exists(path))
{
// Given path is directory, so resolve down to filename
@@ -168,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
else
{
- throw new ResourceNotFoundException();
+ newPath = path;
}
// Write the new ffmpeg path to the xml as <EncoderAppPath>
@@ -183,37 +187,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary>
/// Validates the supplied FQPN to ensure it is a ffmpeg utility.
- /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
+ /// If checks pass, global variable FFmpegPath is updated.
/// </summary>
/// <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)
+ private bool ValidatePath(string path)
{
- bool rc = false;
-
- if (!string.IsNullOrEmpty(path))
+ if (string.IsNullOrEmpty(path))
{
- if (File.Exists(path))
- {
- rc = new EncoderValidator(_logger, path).ValidateVersion();
-
- if (!rc)
- {
- _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
- }
+ return false;
+ }
- _ffmpegPath = path;
- EncoderLocation = location;
- return true;
- }
- else
- {
- _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
- }
+ bool rc = new EncoderValidator(_logger, path).ValidateVersion();
+ if (!rc)
+ {
+ _logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
+ return false;
}
- return rc;
+ _ffmpegPath = path;
+ return true;
}
private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
@@ -234,34 +227,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- /// <summary>
- /// Search the system $PATH environment variable looking for given filename.
- /// </summary>
- /// <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);
- if (!string.IsNullOrEmpty(inJellyfinPath))
- {
- return inJellyfinPath;
- }
-
- var values = Environment.GetEnvironmentVariable("PATH");
-
- foreach (var path in values.Split(Path.PathSeparator))
- {
- var candidatePath = GetEncoderPathFromDirectory(path, fileName);
-
- if (!string.IsNullOrEmpty(candidatePath))
- {
- return candidatePath;
- }
- }
-
- return null;
- }
-
public void SetAvailableEncoders(IEnumerable<string> list)
{
_encoders = list.ToList();
@@ -277,6 +242,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
_hwaccels = list.ToList();
}
+ public void SetAvailableFilters(IEnumerable<string> list)
+ {
+ _filters = list.ToList();
+ }
+
+ public void SetAvailableFiltersWithOption(IDictionary<int, bool> dict)
+ {
+ _filtersWithOption = dict;
+ }
+
+ public void SetMediaEncoderVersion(EncoderValidator validator)
+ {
+ _ffmpegVersion = validator.GetFFmpegVersion();
+ }
+
public bool SupportsEncoder(string encoder)
{
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
@@ -292,17 +272,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
}
- public bool SupportsFilter(string filter, string option)
+ public bool SupportsFilter(string filter)
{
- if (_ffmpegPath != null)
+ return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public bool SupportsFilterWithOption(FilterOptionType option)
+ {
+ if (_filtersWithOption.TryGetValue((int)option, out var val))
{
- var validator = new EncoderValidator(_logger, _ffmpegPath);
- return validator.CheckFilter(filter, option);
+ return val;
}
return false;
}
+ public Version GetMediaEncoderVersion()
+ {
+ return _ffmpegVersion;
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -366,7 +355,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
{
var prefix = "file";
- if (mediaSource.VideoType == VideoType.BluRay)
+ if (mediaSource.VideoType == VideoType.BluRay
+ || mediaSource.IsoType == IsoType.BluRay)
{
prefix = "bluray";
}
@@ -392,7 +382,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var args = extractChapters
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
- args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim();
+ args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
var process = new Process
{
@@ -501,15 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var inputArgument = GetInputArgument(inputFile, mediaSource);
- if (isAudio)
- {
- if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0)
- {
- // It seems for audio files we need to subtract 1 (for the audio stream??)
- imageStreamIndex = imageStreamIndex.Value - 1;
- }
- }
- else
+ if (!isAudio)
{
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
try
@@ -580,7 +562,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_ => string.Empty
};
- var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
+ var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
if (enableHdrExtraction)
@@ -613,7 +595,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
if (offset.HasValue)
{
@@ -726,7 +708,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
Directory.CreateDirectory(targetDirectory);
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads);
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
if (!string.IsNullOrWhiteSpace(container))
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 39fb0b47c..6da9886a4 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -9,9 +9,6 @@
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <AnalysisMode>AllEnabledByDefault</AnalysisMode>
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -29,7 +26,7 @@
<PackageReference Include="libse" Version="3.6.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
- <PackageReference Include="UTF.Unknown" Version="2.3.0" />
+ <PackageReference Include="UTF.Unknown" Version="2.4.0" />
</ItemGroup>
<!-- Code Analyzers-->
diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index da37687e8..9196fe139 100644
--- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.MediaEncoding.Probing
throw new ArgumentNullException(nameof(result));
}
- if (result.Format != null && result.Format.Tags != null)
+ if (result.Format?.Tags != null)
{
result.Format.Tags = ConvertDictionaryToCaseInsensitive(result.Format.Tags);
}
@@ -39,38 +39,16 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
- /// Gets a string from an FFProbeResult tags dictionary.
- /// </summary>
- /// <param name="tags">The tags.</param>
- /// <param name="key">The key.</param>
- /// <returns>System.String.</returns>
- public static string GetDictionaryValue(IReadOnlyDictionary<string, string> tags, string key)
- {
- if (tags == null)
- {
- return null;
- }
-
- tags.TryGetValue(key, out var val);
- return val;
- }
-
- /// <summary>
/// Gets an int from an FFProbeResult tags dictionary.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- public static int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key)
+ public static int? GetDictionaryNumericValue(IReadOnlyDictionary<string, string> tags, string key)
{
- var val = GetDictionaryValue(tags, key);
-
- if (!string.IsNullOrEmpty(val))
+ if (tags.TryGetValue(key, out var val) && int.TryParse(val, out var i))
{
- if (int.TryParse(val, out var i))
- {
- return i;
- }
+ return i;
}
return null;
@@ -82,18 +60,13 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.Nullable{DateTime}.</returns>
- public static DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key)
+ public static DateTime? GetDictionaryDateTime(IReadOnlyDictionary<string, string> tags, string key)
{
- var val = GetDictionaryValue(tags, key);
-
- if (string.IsNullOrEmpty(val))
- {
- return null;
- }
-
- if (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var i))
+ if (tags.TryGetValue(key, out var val)
+ && (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var dateTime)
+ || DateTime.TryParseExact(val, "yyyy", DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out dateTime)))
{
- return i.ToUniversalTime();
+ return dateTime.ToUniversalTime();
}
return null;
diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
index 0e319c1a8..d4d153b08 100644
--- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
+++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System.Collections.Generic;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
index de062d06b..a1cef7a9f 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs
index 8af122ef9..d50da37b8 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System.Collections.Generic;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index 7b7744163..c9c8c34c2 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System.Collections.Generic;
using System.Text.Json.Serialization;
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index a87104cd6..04f0be307 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
@@ -6,7 +7,9 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Xml;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -25,11 +28,13 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private static readonly Regex _performerPattern = new (@"(?<name>.*) \((?<instrument>.*)\)");
+
+ private readonly CultureInfo _usCulture = new ("en-US");
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
- private List<string> _splitWhiteList = null;
+ private string[] _splitWhiteList;
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
{
@@ -37,6 +42,17 @@ namespace MediaBrowser.MediaEncoding.Probing
_localization = localization;
}
+ private IReadOnlyList<string> SplitWhitelist => _splitWhiteList ??= new string[]
+ {
+ "AC/DC",
+ "Au/Ra",
+ "이달의 소녀 1/3",
+ "LOONA 1/3",
+ "LOONA / yyxy",
+ "LOONA / ODD EYE CIRCLE",
+ "K/DA"
+ };
+
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
{
var info = new MediaInfo
@@ -57,7 +73,7 @@ namespace MediaBrowser.MediaEncoding.Probing
.Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec))
.ToList();
- info.MediaAttachments = internalStreams.Select(s => GetMediaAttachment(s))
+ info.MediaAttachments = internalStreams.Select(GetMediaAttachment)
.Where(i => i != null)
.ToList();
@@ -77,63 +93,66 @@ namespace MediaBrowser.MediaEncoding.Probing
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var tagStreamType = isAudio ? "audio" : "video";
- if (data.Streams != null)
- {
- var tagStream = data.Streams.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase));
+ var tagStream = data.Streams?.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase));
- if (tagStream != null && tagStream.Tags != null)
+ if (tagStream?.Tags != null)
+ {
+ foreach (var (key, value) in tagStream.Tags)
{
- foreach (var pair in tagStream.Tags)
- {
- tags[pair.Key] = pair.Value;
- }
+ tags[key] = value;
}
}
- if (data.Format != null && data.Format.Tags != null)
+ if (data.Format?.Tags != null)
{
- foreach (var pair in data.Format.Tags)
+ foreach (var (key, value) in data.Format.Tags)
{
- tags[pair.Key] = pair.Value;
+ tags[key] = value;
}
}
FetchGenres(info, tags);
- var overview = FFProbeHelpers.GetDictionaryValue(tags, "synopsis");
- if (string.IsNullOrWhiteSpace(overview))
- {
- overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
- }
-
- if (string.IsNullOrWhiteSpace(overview))
- {
- overview = FFProbeHelpers.GetDictionaryValue(tags, "desc");
- }
-
- if (!string.IsNullOrWhiteSpace(overview))
- {
- info.Overview = overview;
- }
-
- var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
- if (!string.IsNullOrWhiteSpace(title))
- {
- info.Name = title;
- }
+ info.Name = tags.GetFirstNotNullNorWhiteSpaceValue("title", "title-eng");
+ info.ForcedSortName = tags.GetFirstNotNullNorWhiteSpaceValue("sort_name", "title-sort", "titlesort");
+ info.Overview = tags.GetFirstNotNullNorWhiteSpaceValue("synopsis", "description", "desc");
info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort");
info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number");
- info.ShowName = FFProbeHelpers.GetDictionaryValue(tags, "show_name");
+ info.ShowName = tags.GetValueOrDefault("show_name");
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
- // Several different forms of retaildate
- info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
+ // Several different forms of retail/premiere date
+ info.PremiereDate =
+ FFProbeHelpers.GetDictionaryDateTime(tags, "originaldate") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
+ // Set common metadata for music (audio) and music videos (video)
+ info.Album = tags.GetValueOrDefault("album");
+
+ if (tags.TryGetValue("artists", out var artists) && !string.IsNullOrWhiteSpace(artists))
+ {
+ info.Artists = SplitDistinctArtists(artists, new[] { '/', ';' }, false).ToArray();
+ }
+ else
+ {
+ var artist = tags.GetFirstNotNullNorWhiteSpaceValue("artist");
+ info.Artists = artist == null
+ ? Array.Empty<string>()
+ : SplitDistinctArtists(artist, _nameDelimiters, true).ToArray();
+ }
+
+ // Guess ProductionYear from PremiereDate if missing
+ if (!info.ProductionYear.HasValue && info.PremiereDate.HasValue)
+ {
+ info.ProductionYear = info.PremiereDate.Value.Year;
+ }
+
+ // Set mediaType-specific metadata
if (isAudio)
{
SetAudioRuntimeTicks(data, info);
@@ -147,10 +166,10 @@ namespace MediaBrowser.MediaEncoding.Probing
{
FetchStudios(info, tags, "copyright");
- var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC");
- if (!string.IsNullOrWhiteSpace(iTunEXTC))
+ var iTunExtc = tags.GetFirstNotNullNorWhiteSpaceValue("iTunEXTC");
+ if (iTunExtc != null)
{
- var parts = iTunEXTC.Split('|', StringSplitOptions.RemoveEmptyEntries);
+ var parts = iTunExtc.Split('|', StringSplitOptions.RemoveEmptyEntries);
// Example
// mpaa|G|100|For crude humor
if (parts.Length > 1)
@@ -164,10 +183,10 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI");
- if (!string.IsNullOrWhiteSpace(itunesXml))
+ var iTunXml = tags.GetFirstNotNullNorWhiteSpaceValue("iTunMOVI");
+ if (iTunXml != null)
{
- FetchFromItunesInfo(itunesXml, info);
+ FetchFromItunesInfo(iTunXml, info);
}
if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration))
@@ -184,8 +203,7 @@ namespace MediaBrowser.MediaEncoding.Probing
ExtractTimestamp(info);
- var stereoMode = GetDictionaryValue(tags, "stereo_mode");
- if (string.Equals(stereoMode, "left_right", StringComparison.OrdinalIgnoreCase))
+ if (tags.TryGetValue("stereo_mode", out var stereoMode) && string.Equals(stereoMode, "left_right", StringComparison.OrdinalIgnoreCase))
{
info.Video3DFormat = Video3DFormat.FullSideBySide;
}
@@ -238,42 +256,36 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
{
- if (channelsValue <= 2)
- {
- return 192000;
- }
-
- if (channelsValue >= 5)
+ switch (channelsValue)
{
- return 320000;
+ case <= 2:
+ return 192000;
+ case >= 5:
+ return 320000;
}
}
if (string.Equals(codec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
{
- if (channelsValue <= 2)
- {
- return 192000;
- }
-
- if (channelsValue >= 5)
+ switch (channelsValue)
{
- return 640000;
+ case <= 2:
+ return 192000;
+ case >= 5:
+ return 640000;
}
}
if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
{
- if (channelsValue <= 2)
+ switch (channelsValue)
{
- return 960000;
- }
-
- if (channelsValue >= 5)
- {
- return 2880000;
+ case <= 2:
+ return 960000;
+ case >= 5:
+ return 2880000;
}
}
@@ -731,6 +743,23 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.BitDepth = streamInfo.BitsPerRawSample;
}
+ if (!stream.BitDepth.HasValue)
+ {
+ if (!string.IsNullOrEmpty(streamInfo.PixelFormat)
+ && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 10;
+ }
+
+ if (!string.IsNullOrEmpty(streamInfo.Profile)
+ && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+ || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
+ || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase)))
+ {
+ stream.BitDepth = 10;
+ }
+ }
+
// 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);
@@ -803,7 +832,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|| string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)))
{
var bps = GetBPSFromTags(streamInfo);
- if (bps != null && bps > 0)
+ if (bps > 0)
{
stream.BitRate = bps;
}
@@ -872,6 +901,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
tags.TryGetValue(key, out var val);
+
return val;
}
@@ -879,7 +909,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
if (string.IsNullOrEmpty(input))
{
- return input;
+ return null;
}
return input.Split('(').FirstOrDefault();
@@ -967,64 +997,64 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>System.Nullable{System.Single}.</returns>
private float? GetFrameRate(string value)
{
- if (!string.IsNullOrEmpty(value))
+ if (string.IsNullOrEmpty(value))
{
- var parts = value.Split('/');
+ return null;
+ }
- float result;
+ var parts = value.Split('/');
- if (parts.Length == 2)
- {
- result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture);
- }
- else
- {
- result = float.Parse(parts[0], _usCulture);
- }
+ float result;
- return float.IsNaN(result) ? (float?)null : result;
+ if (parts.Length == 2)
+ {
+ result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture);
+ }
+ else
+ {
+ result = float.Parse(parts[0], _usCulture);
}
- return null;
+ return float.IsNaN(result) ? null : result;
}
private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data)
{
- if (result.Streams != null)
+ // Get the first info stream
+ var stream = result.Streams?.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase));
+ if (stream == null)
{
- // Get the first info stream
- var stream = result.Streams.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase));
+ return;
+ }
- if (stream != null)
- {
- // Get duration from stream properties
- var duration = stream.Duration;
+ // Get duration from stream properties
+ var duration = stream.Duration;
- // If it's not there go into format properties
- if (string.IsNullOrEmpty(duration))
- {
- duration = result.Format.Duration;
- }
+ // If it's not there go into format properties
+ if (string.IsNullOrEmpty(duration))
+ {
+ duration = result.Format.Duration;
+ }
- // If we got something, parse it
- if (!string.IsNullOrEmpty(duration))
- {
- data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
- }
- }
+ // If we got something, parse it
+ if (!string.IsNullOrEmpty(duration))
+ {
+ data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
}
}
private int? GetBPSFromTags(MediaStreamInfo streamInfo)
{
- if (streamInfo != null && streamInfo.Tags != null)
+ if (streamInfo?.Tags == null)
{
- var bps = GetDictionaryValue(streamInfo.Tags, "BPS-eng") ?? GetDictionaryValue(streamInfo.Tags, "BPS");
- if (!string.IsNullOrEmpty(bps)
- && int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps))
- {
- return parsedBps;
- }
+ return null;
+ }
+
+ var bps = GetDictionaryValue(streamInfo.Tags, "BPS-eng") ?? GetDictionaryValue(streamInfo.Tags, "BPS");
+ if (!string.IsNullOrEmpty(bps)
+ && int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps))
+ {
+ return parsedBps;
}
return null;
@@ -1032,13 +1062,15 @@ namespace MediaBrowser.MediaEncoding.Probing
private double? GetRuntimeSecondsFromTags(MediaStreamInfo streamInfo)
{
- if (streamInfo != null && streamInfo.Tags != null)
+ if (streamInfo?.Tags == null)
{
- var duration = GetDictionaryValue(streamInfo.Tags, "DURATION-eng") ?? GetDictionaryValue(streamInfo.Tags, "DURATION");
- if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var parsedDuration))
- {
- return parsedDuration.TotalSeconds;
- }
+ return null;
+ }
+
+ var duration = GetDictionaryValue(streamInfo.Tags, "DURATION-eng") ?? GetDictionaryValue(streamInfo.Tags, "DURATION");
+ if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var parsedDuration))
+ {
+ return parsedDuration.TotalSeconds;
}
return null;
@@ -1046,14 +1078,17 @@ namespace MediaBrowser.MediaEncoding.Probing
private long? GetNumberOfBytesFromTags(MediaStreamInfo streamInfo)
{
- if (streamInfo != null && streamInfo.Tags != null)
+ if (streamInfo?.Tags == null)
{
- var numberOfBytes = GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES-eng") ?? GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES");
- if (!string.IsNullOrEmpty(numberOfBytes)
- && long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes))
- {
- return parsedBytes;
- }
+ return null;
+ }
+
+ var numberOfBytes = GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES-eng")
+ ?? GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES");
+ if (!string.IsNullOrEmpty(numberOfBytes)
+ && long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes))
+ {
+ return parsedBytes;
}
return null;
@@ -1061,124 +1096,120 @@ namespace MediaBrowser.MediaEncoding.Probing
private void SetSize(InternalMediaInfoResult data, MediaInfo info)
{
- if (data.Format != null)
+ if (data.Format == null)
{
- if (!string.IsNullOrEmpty(data.Format.Size))
- {
- info.Size = long.Parse(data.Format.Size, _usCulture);
- }
- else
- {
- info.Size = null;
- }
+ return;
}
+
+ info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, _usCulture);
}
- private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
+ private void SetAudioInfoFromTags(MediaInfo audio, IReadOnlyDictionary<string, string> tags)
{
- var peoples = new List<BaseItemPerson>();
- var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
- if (!string.IsNullOrWhiteSpace(composer))
+ var people = new List<BaseItemPerson>();
+ if (tags.TryGetValue("composer", out var composer) && !string.IsNullOrWhiteSpace(composer))
{
foreach (var person in Split(composer, false))
{
- peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
}
}
- var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
- if (!string.IsNullOrWhiteSpace(conductor))
+ if (tags.TryGetValue("conductor", out var conductor) && !string.IsNullOrWhiteSpace(conductor))
{
foreach (var person in Split(conductor, false))
{
- peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
}
}
- var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
- if (!string.IsNullOrWhiteSpace(lyricist))
+ if (tags.TryGetValue("lyricist", out var lyricist) && !string.IsNullOrWhiteSpace(lyricist))
{
foreach (var person in Split(lyricist, false))
{
- peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
}
}
- // Check for writer some music is tagged that way as alternative to composer/lyricist
- var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
-
- if (!string.IsNullOrWhiteSpace(writer))
+ if (tags.TryGetValue("performer", out var performer) && !string.IsNullOrWhiteSpace(performer))
{
- foreach (var person in Split(writer, false))
+ foreach (var person in Split(performer, false))
{
- peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
+ Match match = _performerPattern.Match(person);
+
+ // If the performer doesn't have any instrument/role associated, it won't match. In that case, chances are it's simply a band name, so we skip it.
+ if (match.Success)
+ {
+ people.Add(new BaseItemPerson
+ {
+ Name = match.Groups["name"].Value,
+ Type = PersonType.Actor,
+ Role = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value)
+ });
+ }
}
}
- audio.People = peoples.ToArray();
- audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
-
- var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
-
- if (!string.IsNullOrWhiteSpace(artists))
- {
- audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false)
- .DistinctNames()
- .ToArray();
- }
- else
+ // In cases where there isn't sufficient information as to which role a writer performed on a recording, tagging software uses the "writer" tag.
+ if (tags.TryGetValue("writer", out var writer) && !string.IsNullOrWhiteSpace(writer))
{
- var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
- if (string.IsNullOrWhiteSpace(artist))
- {
- audio.Artists = Array.Empty<string>();
- }
- else
+ foreach (var person in Split(writer, false))
{
- audio.Artists = SplitArtists(artist, _nameDelimiters, true)
- .DistinctNames()
- .ToArray();
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
}
}
- var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist");
- if (string.IsNullOrWhiteSpace(albumArtist))
+ if (tags.TryGetValue("arranger", out var arranger) && !string.IsNullOrWhiteSpace(arranger))
{
- albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
+ foreach (var person in Split(arranger, false))
+ {
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Arranger });
+ }
}
- if (string.IsNullOrWhiteSpace(albumArtist))
+ if (tags.TryGetValue("engineer", out var engineer) && !string.IsNullOrWhiteSpace(engineer))
{
- albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
+ foreach (var person in Split(engineer, false))
+ {
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Engineer });
+ }
}
- if (string.IsNullOrWhiteSpace(albumArtist))
+ if (tags.TryGetValue("mixer", out var mixer) && !string.IsNullOrWhiteSpace(mixer))
{
- audio.AlbumArtists = Array.Empty<string>();
+ foreach (var person in Split(mixer, false))
+ {
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Mixer });
+ }
}
- else
+
+ if (tags.TryGetValue("remixer", out var remixer) && !string.IsNullOrWhiteSpace(remixer))
{
- audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true)
- .DistinctNames()
- .ToArray();
+ foreach (var person in Split(remixer, false))
+ {
+ people.Add(new BaseItemPerson { Name = person, Type = PersonType.Remixer });
+ }
}
+ audio.People = people.ToArray();
+
+ // Set album artist
+ var albumArtist = tags.GetFirstNotNullNorWhiteSpaceValue("albumartist", "album artist", "album_artist");
+ audio.AlbumArtists = albumArtist != null
+ ? SplitDistinctArtists(albumArtist, _nameDelimiters, true).ToArray()
+ : Array.Empty<string>();
+
+ // Set album artist to artist if empty
if (audio.AlbumArtists.Length == 0)
{
audio.AlbumArtists = audio.Artists;
}
// Track number
- audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
+ audio.IndexNumber = GetDictionaryTrackOrDiscNumber(tags, "track");
// Disc number
- audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
-
- // If we don't have a ProductionYear try and get it from PremiereDate
- if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
- {
- audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
- }
+ audio.ParentIndexNumber = GetDictionaryTrackOrDiscNumber(tags, "disc");
// There's several values in tags may or may not be present
FetchStudios(audio, tags, "organization");
@@ -1186,45 +1217,25 @@ namespace MediaBrowser.MediaEncoding.Probing
FetchStudios(audio, tags, "publisher");
FetchStudios(audio, tags, "label");
- // 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"));
- }
-
+ // These support multiple values, but for now we only store the first.
+ var mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Album Artist Id"))
+ ?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("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"));
- }
-
+ mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Artist Id"))
+ ?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("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"));
- }
-
+ mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Album Id"))
+ ?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("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"));
- }
-
+ mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Release Group Id"))
+ ?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("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"));
- }
-
+ mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Release Track Id"))
+ ?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_RELEASETRACKID"));
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb);
}
@@ -1248,18 +1259,18 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>System.String[][].</returns>
private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
{
- // Only use the comma as a delimeter if there are no slashes or pipes.
+ // Only use the comma as a delimiter 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, StringComparison.Ordinal) != -1) ?
+ var delimiter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i, StringComparison.Ordinal) != -1) ?
_nameDelimiters :
new[] { ',' };
- return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
+ return val.Split(delimiter, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim());
}
- private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
+ private IEnumerable<string> SplitDistinctArtists(string val, char[] delimiters, bool splitFeaturing)
{
if (splitFeaturing)
{
@@ -1269,7 +1280,7 @@ namespace MediaBrowser.MediaEncoding.Probing
var artistsFound = new List<string>();
- foreach (var whitelistArtist in GetSplitWhitelist())
+ foreach (var whitelistArtist in SplitWhitelist)
{
var originalVal = val;
val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase);
@@ -1285,20 +1296,7 @@ namespace MediaBrowser.MediaEncoding.Probing
.Select(i => i.Trim());
artistsFound.AddRange(artists);
- return artistsFound;
- }
-
- private IEnumerable<string> GetSplitWhitelist()
- {
- if (_splitWhiteList == null)
- {
- _splitWhiteList = new List<string>
- {
- "AC/DC"
- };
- }
-
- return _splitWhiteList;
+ return artistsFound.DistinctNames();
}
/// <summary>
@@ -1307,36 +1305,38 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <param name="info">The info.</param>
/// <param name="tags">The tags.</param>
/// <param name="tagName">Name of the tag.</param>
- private void FetchStudios(MediaInfo info, Dictionary<string, string> tags, string tagName)
+ private void FetchStudios(MediaInfo info, IReadOnlyDictionary<string, string> tags, string tagName)
{
- var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
+ var val = tags.GetValueOrDefault(tagName);
- if (!string.IsNullOrEmpty(val))
+ if (string.IsNullOrEmpty(val))
{
- var studios = Split(val, true);
- var studioList = new List<string>();
+ return;
+ }
- foreach (var studio in studios)
- {
- // Sometimes the artist name is listed here, account for that
- if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase))
- {
- continue;
- }
+ var studios = Split(val, true);
+ var studioList = new List<string>();
- if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
- {
- continue;
- }
+ foreach (var studio in studios)
+ {
+ if (string.IsNullOrWhiteSpace(studio))
+ {
+ continue;
+ }
- studioList.Add(studio);
+ // Don't add artist/album artist name to studios, even if it's listed there
+ if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase)
+ || info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
+ {
+ continue;
}
- info.Studios = studioList
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToArray();
+ studioList.Add(studio);
}
+
+ info.Studios = studioList
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
}
/// <summary>
@@ -1344,58 +1344,55 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
- private void FetchGenres(MediaInfo info, Dictionary<string, string> tags)
+ private void FetchGenres(MediaInfo info, IReadOnlyDictionary<string, string> tags)
{
- var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
+ var genreVal = tags.GetValueOrDefault("genre");
+ if (string.IsNullOrEmpty(genreVal))
+ {
+ return;
+ }
- if (!string.IsNullOrEmpty(val))
+ var genres = new List<string>(info.Genres);
+ foreach (var genre in Split(genreVal, true))
{
- var genres = new List<string>(info.Genres);
- foreach (var genre in Split(val, true))
+ if (string.IsNullOrWhiteSpace(genre))
{
- genres.Add(genre);
+ continue;
}
- info.Genres = genres
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToArray();
+ genres.Add(genre);
}
+
+ info.Genres = genres
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
}
/// <summary>
- /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'.
+ /// Gets the track or disc number, which 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>
- /// <returns>System.Nullable{System.Int32}.</returns>
- private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
+ /// <returns>The track or disc number, or null, if missing or not parseable.</returns>
+ private static int? GetDictionaryTrackOrDiscNumber(IReadOnlyDictionary<string, string> tags, string tagName)
{
- var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
+ var disc = tags.GetValueOrDefault(tagName);
- if (!string.IsNullOrEmpty(disc))
+ if (!string.IsNullOrEmpty(disc) && int.TryParse(disc.Split('/')[0], out var discNum))
{
- disc = disc.Split('/')[0];
-
- if (int.TryParse(disc, out var num))
- {
- return num;
- }
+ return discNum;
}
return null;
}
- private ChapterInfo GetChapterInfo(MediaChapter chapter)
+ private static ChapterInfo GetChapterInfo(MediaChapter chapter)
{
var info = new ChapterInfo();
- if (chapter.Tags != null)
+ if (chapter.Tags != null && chapter.Tags.TryGetValue("title", out string name))
{
- if (chapter.Tags.TryGetValue("title", out string name))
- {
- info.Name = name;
- }
+ info.Name = name;
}
// Limit accuracy to milliseconds to match xml saving
@@ -1412,14 +1409,14 @@ namespace MediaBrowser.MediaEncoding.Probing
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
{
- if (data.Format == null || data.Format.Tags == null)
+ var tags = data.Format?.Tags;
+
+ if (tags == null)
{
return;
}
- var genres = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/Genre");
-
- if (!string.IsNullOrWhiteSpace(genres))
+ if (tags.TryGetValue("WM/Genre", out var genres) && !string.IsNullOrWhiteSpace(genres))
{
var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
@@ -1433,16 +1430,12 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- var officialRating = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/ParentalRating");
-
- if (!string.IsNullOrWhiteSpace(officialRating))
+ if (tags.TryGetValue("WM/ParentalRating", out var officialRating) && !string.IsNullOrWhiteSpace(officialRating))
{
video.OfficialRating = officialRating;
}
- var people = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaCredits");
-
- if (!string.IsNullOrEmpty(people))
+ if (tags.TryGetValue("WM/MediaCredits", out var people) && !string.IsNullOrEmpty(people))
{
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
@@ -1450,29 +1443,21 @@ namespace MediaBrowser.MediaEncoding.Probing
.ToArray();
}
- var year = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/OriginalReleaseTime");
- if (!string.IsNullOrWhiteSpace(year))
+ if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, _usCulture, out var parsedYear))
{
- if (int.TryParse(year, NumberStyles.Integer, _usCulture, out var val))
- {
- video.ProductionYear = val;
- }
+ video.ProductionYear = parsedYear;
}
- var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaOriginalBroadcastDateTime");
- if (!string.IsNullOrWhiteSpace(premiereDateString))
+ // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
+ // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
+ if (tags.TryGetValue("WM/MediaOriginalBroadcastDateTime", out var premiereDateString) && DateTime.TryParse(year, null, DateTimeStyles.None, out var parsedDate))
{
- // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
- // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
- if (DateTime.TryParse(year, null, DateTimeStyles.None, out var val))
- {
- video.PremiereDate = val.ToUniversalTime();
- }
+ video.PremiereDate = parsedDate.ToUniversalTime();
}
- var description = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitleDescription");
+ var description = tags.GetValueOrDefault("WM/SubTitleDescription");
- var subTitle = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitle");
+ var subTitle = tags.GetValueOrDefault("WM/SubTitle");
// For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
@@ -1483,49 +1468,48 @@ namespace MediaBrowser.MediaEncoding.Probing
// 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.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
+ && description.AsSpan()[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)
+ string[] descriptionParts = description.Split(':');
+ if (descriptionParts.Length > 0)
{
- string subtitle = parts[0];
+ string subtitle = descriptionParts[0];
try
{
- if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
+ // Check if it contains a episode number and season number
+ if (subtitle.Contains('/', StringComparison.Ordinal))
{
- string[] numbers = subtitle.Split(' ');
- 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);
+ string[] subtitleParts = subtitle.Split(' ');
+ string[] numbers = subtitleParts[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/');
+ video.IndexNumber = int.Parse(numbers[0], CultureInfo.InvariantCulture);
+ // int totalEpisodesInSeason = int.Parse(numbers[1], CultureInfo.InvariantCulture);
- description = string.Join(' ', numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
+ // Skip the numbers, concatenate the rest, trim and set as new description
+ description = string.Join(' ', subtitleParts, 1, subtitleParts.Length - 1).Trim();
+ }
+ else if (subtitle.Contains('.', StringComparison.Ordinal))
+ {
+ var subtitleParts = subtitle.Split('.');
+ description = string.Join('.', subtitleParts, 1, subtitleParts.Length - 1).Trim();
}
else
{
- // Switch to default parsing
- 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
- }
+ description = subtitle.Trim();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while parsing subtitle field");
- // Default parsing
+ // Fallback to default parsing
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
+ var subtitleParts = subtitle.Split('.');
+ description = string.Join('.', subtitleParts, 1, subtitleParts.Length - 1).Trim();
}
else
{
- description = subtitle.Trim(); // Clean up whitespaces and save it
+ description = subtitle.Trim();
}
}
}
@@ -1539,24 +1523,27 @@ namespace MediaBrowser.MediaEncoding.Probing
private void ExtractTimestamp(MediaInfo video)
{
- if (video.VideoType == VideoType.VideoFile)
+ if (video.VideoType != VideoType.VideoFile)
{
- if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
- {
- try
- {
- video.Timestamp = GetMpegTimestamp(video.Path);
+ return;
+ }
- _logger.LogDebug("Video has {Timestamp} timestamp", video.Timestamp);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error extracting timestamp info from {Path}", video.Path);
- video.Timestamp = null;
- }
- }
+ if (!string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ try
+ {
+ video.Timestamp = GetMpegTimestamp(video.Path);
+ _logger.LogDebug("Video has {Timestamp} timestamp", video.Timestamp);
+ }
+ catch (Exception ex)
+ {
+ video.Timestamp = null;
+ _logger.LogError(ex, "Error extracting timestamp info from {Path}", video.Path);
}
}
@@ -1575,17 +1562,17 @@ namespace MediaBrowser.MediaEncoding.Probing
return TransportStreamTimestamp.None;
}
- if ((packetBuffer[4] == 71) && (packetBuffer[196] == 71))
+ if ((packetBuffer[4] != 71) || (packetBuffer[196] != 71))
{
- if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
- {
- return TransportStreamTimestamp.Zero;
- }
+ return TransportStreamTimestamp.None;
+ }
- return TransportStreamTimestamp.Valid;
+ if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
+ {
+ return TransportStreamTimestamp.Zero;
}
- return TransportStreamTimestamp.None;
+ return TransportStreamTimestamp.Valid;
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index 8219aa7b4..08ee5c72e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs
deleted file mode 100644
index dca5c1e8a..000000000
--- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-#nullable enable
-#pragma warning disable CS1591
-
-namespace MediaBrowser.MediaEncoding.Subtitles
-{
- 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 19fb951dc..78d54ca51 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index 36dc2e01f..17c2ae40e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -1,5 +1,3 @@
-#nullable enable
-
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
index 82ec6ca21..24ceb1b57 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
@@ -1,10 +1,8 @@
-#nullable enable
-
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
-using MediaBrowser.Common.Extensions;
+using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 39bec8da1..608ebf443 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -71,8 +72,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- var reader = GetReader(inputFormat, true);
-
+ var reader = GetReader(inputFormat);
var trackInfo = reader.Parse(stream, cancellationToken);
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
@@ -139,10 +139,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
.ConfigureAwait(false);
var inputFormat = subtitle.format;
- var writer = TryGetWriter(outputFormat);
// Return the original if we don't have any way of converting it
- if (writer == null)
+ if (!TryGetWriter(outputFormat, out var writer))
{
return subtitle.stream;
}
@@ -239,7 +238,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
.TrimStart('.');
- if (GetReader(currentFormat, false) == null)
+ if (TryGetReader(currentFormat, out _))
{
// Convert
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
@@ -257,37 +256,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return new SubtitleInfo(subtitleStream.Path, mediaSource.Protocol, currentFormat, true);
}
- private ISubtitleParser GetReader(string format, bool throwIfMissing)
+ private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value)
{
- if (string.IsNullOrEmpty(format))
- {
- throw new ArgumentNullException(nameof(format));
- }
-
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
- return new SrtParser(_logger);
+ value = new SrtParser(_logger);
+ return true;
}
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
- return new SsaParser(_logger);
+ value = new SsaParser(_logger);
+ return true;
}
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
- return new AssParser(_logger);
+ value = new AssParser(_logger);
+ return true;
}
- if (throwIfMissing)
+ value = null;
+ return false;
+ }
+
+ private ISubtitleParser GetReader(string format)
+ {
+ if (TryGetReader(format, out var reader))
{
- throw new ArgumentException("Unsupported format: " + format);
+ return reader;
}
- return null;
+ throw new ArgumentException("Unsupported format: " + format);
}
- private ISubtitleWriter TryGetWriter(string format)
+ private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
{
if (string.IsNullOrEmpty(format))
{
@@ -296,32 +299,35 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{
- return new JsonWriter();
+ value = new JsonWriter();
+ return true;
}
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
- return new SrtWriter();
+ value = new SrtWriter();
+ return true;
}
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
{
- return new VttWriter();
+ value = new VttWriter();
+ return true;
}
if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase))
{
- return new TtmlWriter();
+ value = new TtmlWriter();
+ return true;
}
- return null;
+ value = null;
+ return false;
}
private ISubtitleWriter GetWriter(string format)
{
- var writer = TryGetWriter(format);
-
- if (writer != null)
+ if (TryGetWriter(format, out var writer))
{
return writer;
}
@@ -391,7 +397,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException(nameof(outputPath));
}
- Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+ Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false);
@@ -549,7 +555,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException(nameof(outputPath));
}
- Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+ Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
var processArgs = string.Format(
CultureInfo.InvariantCulture,
@@ -715,7 +721,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
- var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
+ var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty;
// UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding
if ((path.EndsWith(".ass", StringComparison.Ordinal) || path.EndsWith(".ssa", StringComparison.Ordinal) || path.EndsWith(".srt", StringComparison.Ordinal))
@@ -725,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
charset = string.Empty;
}
- _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
+ _logger.LogDebug("charset {0} detected for {Path}", charset, path);
return charset;
}