aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorRonan Charles-Lorel <roro.roronoa@gmail.com>2023-06-29 15:08:52 +0200
committerGitHub <noreply@github.com>2023-06-29 15:08:52 +0200
commite108183b138552013bbfd13c36937481228eb9e6 (patch)
tree8e374adf35d64b157ac88e5b84a25d186bd4ccf1 /MediaBrowser.MediaEncoding
parent31ac861b8560547c7e0c46513077abf76e6bc618 (diff)
parentb5bbb98175e0542d43c01f80c15e8dce04e58b53 (diff)
Merge branch 'jellyfin:master' into master
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs17
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs122
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs187
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs68
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs30
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs29
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs179
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj17
-rw-r--r--MediaBrowser.MediaEncoding/Probing/CodecType.cs32
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs167
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs8
12 files changed, 698 insertions, 164 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index db177ff76..989e386a5 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -230,10 +231,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
}
- else
- {
- _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
- }
+
+ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
}
private async Task<Stream> GetAttachmentStream(
@@ -301,10 +300,10 @@ namespace MediaBrowser.MediaEncoding.Attachments
var processArgs = string.Format(
CultureInfo.InvariantCulture,
- "-dump_attachment:{1} {2} -i {0} -t 0 -f null null",
+ "-dump_attachment:{1} \"{2}\" -i {0} -t 0 -f null null",
inputPath,
attachmentStreamIndex,
- outputPath);
+ EncodingUtils.NormalizePath(outputPath));
int exitCode;
@@ -375,10 +374,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
}
- else
- {
- _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
- }
+
+ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
}
private string GetAttachmentCachePath(string mediaPath, MediaSourceInfo mediaSource, int attachmentStreamIndex)
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
new file mode 100644
index 000000000..fca17d4c0
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -0,0 +1,122 @@
+using System.IO;
+using System.Linq;
+using BDInfo.IO;
+using MediaBrowser.Model.IO;
+
+namespace MediaBrowser.MediaEncoding.BdInfo;
+
+/// <summary>
+/// Class BdInfoDirectoryInfo.
+/// </summary>
+public class BdInfoDirectoryInfo : IDirectoryInfo
+{
+ private readonly IFileSystem _fileSystem;
+
+ private readonly FileSystemMetadata _impl;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BdInfoDirectoryInfo" /> class.
+ /// </summary>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="path">The path.</param>
+ public BdInfoDirectoryInfo(IFileSystem fileSystem, string path)
+ {
+ _fileSystem = fileSystem;
+ _impl = _fileSystem.GetDirectoryInfo(path);
+ }
+
+ private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl)
+ {
+ _fileSystem = fileSystem;
+ _impl = impl;
+ }
+
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ public string Name => _impl.Name;
+
+ /// <summary>
+ /// Gets the full name.
+ /// </summary>
+ public string FullName => _impl.FullName;
+
+ /// <summary>
+ /// Gets the parent directory information.
+ /// </summary>
+ public IDirectoryInfo? Parent
+ {
+ get
+ {
+ var parentFolder = Path.GetDirectoryName(_impl.FullName);
+ if (parentFolder is not null)
+ {
+ return new BdInfoDirectoryInfo(_fileSystem, parentFolder);
+ }
+
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the directories.
+ /// </summary>
+ /// <returns>An array with all directories.</returns>
+ public IDirectoryInfo[] GetDirectories()
+ {
+ return _fileSystem.GetDirectories(_impl.FullName)
+ .Select(x => new BdInfoDirectoryInfo(_fileSystem, x))
+ .ToArray();
+ }
+
+ /// <summary>
+ /// Gets the files.
+ /// </summary>
+ /// <returns>All files of the directory.</returns>
+ public IFileInfo[] GetFiles()
+ {
+ return _fileSystem.GetFiles(_impl.FullName)
+ .Select(x => new BdInfoFileInfo(x))
+ .ToArray();
+ }
+
+ /// <summary>
+ /// Gets the files matching a pattern.
+ /// </summary>
+ /// <param name="searchPattern">The search pattern.</param>
+ /// <returns>All files of the directory matchign the search pattern.</returns>
+ public IFileInfo[] GetFiles(string searchPattern)
+ {
+ return _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false)
+ .Select(x => new BdInfoFileInfo(x))
+ .ToArray();
+ }
+
+ /// <summary>
+ /// Gets the files matching a pattern and search options.
+ /// </summary>
+ /// <param name="searchPattern">The search pattern.</param>
+ /// <param name="searchOption">The search optin.</param>
+ /// <returns>All files of the directory matchign the search pattern and options.</returns>
+ public IFileInfo[] GetFiles(string searchPattern, SearchOption searchOption)
+ {
+ return _fileSystem.GetFiles(
+ _impl.FullName,
+ new[] { searchPattern },
+ false,
+ searchOption == SearchOption.AllDirectories)
+ .Select(x => new BdInfoFileInfo(x))
+ .ToArray();
+ }
+
+ /// <summary>
+ /// Gets the bdinfo of a file system path.
+ /// </summary>
+ /// <param name="fs">The file system.</param>
+ /// <param name="path">The path.</param>
+ /// <returns>The BD directory information of the path on the file system.</returns>
+ public static IDirectoryInfo FromFileSystemPath(IFileSystem fs, string path)
+ {
+ return new BdInfoDirectoryInfo(fs, path);
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
new file mode 100644
index 000000000..8ebb59c59
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BDInfo;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.MediaInfo;
+
+namespace MediaBrowser.MediaEncoding.BdInfo;
+
+/// <summary>
+/// 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;
+ }
+
+ /// <summary>
+ /// Gets the disc info.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>BlurayDiscInfo.</returns>
+ public BlurayDiscInfo GetDiscInfo(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path));
+
+ bdrom.Scan();
+
+ // Get the longest playlist
+ var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid);
+
+ var outputStream = new BlurayDiscInfo
+ {
+ MediaStreams = Array.Empty<MediaStream>()
+ };
+
+ if (playlist is null)
+ {
+ return outputStream;
+ }
+
+ outputStream.Chapters = playlist.Chapters.ToArray();
+
+ outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks;
+
+ var sortedStreams = playlist.SortedStreams;
+ var mediaStreams = new List<MediaStream>(sortedStreams.Count);
+
+ foreach (var stream in sortedStreams)
+ {
+ switch (stream)
+ {
+ case TSVideoStream videoStream:
+ AddVideoStream(mediaStreams, videoStream);
+ break;
+ case TSAudioStream audioStream:
+ AddAudioStream(mediaStreams, audioStream);
+ break;
+ case TSTextStream textStream:
+ AddSubtitleStream(mediaStreams, textStream);
+ break;
+ case TSGraphicsStream graphicStream:
+ AddSubtitleStream(mediaStreams, graphicStream);
+ break;
+ }
+ }
+
+ outputStream.MediaStreams = mediaStreams.ToArray();
+
+ outputStream.PlaylistName = playlist.Name;
+
+ if (playlist.StreamClips is not null && playlist.StreamClips.Count > 0)
+ {
+ // Get the files in the playlist
+ outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray();
+ }
+
+ return outputStream;
+ }
+
+ /// <summary>
+ /// Adds the video stream.
+ /// </summary>
+ /// <param name="streams">The streams.</param>
+ /// <param name="videoStream">The video stream.</param>
+ private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream)
+ {
+ var mediaStream = new MediaStream
+ {
+ BitRate = Convert.ToInt32(videoStream.BitRate),
+ Width = videoStream.Width,
+ Height = videoStream.Height,
+ Codec = videoStream.CodecShortName,
+ IsInterlaced = videoStream.IsInterlaced,
+ Type = MediaStreamType.Video,
+ Index = streams.Count
+ };
+
+ if (videoStream.FrameRateDenominator > 0)
+ {
+ float frameRateEnumerator = videoStream.FrameRateEnumerator;
+ float frameRateDenominator = videoStream.FrameRateDenominator;
+
+ mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator;
+ }
+
+ streams.Add(mediaStream);
+ }
+
+ /// <summary>
+ /// Adds the audio stream.
+ /// </summary>
+ /// <param name="streams">The streams.</param>
+ /// <param name="audioStream">The audio stream.</param>
+ private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream)
+ {
+ var stream = new MediaStream
+ {
+ Codec = audioStream.CodecShortName,
+ Language = audioStream.LanguageCode,
+ Channels = audioStream.ChannelCount,
+ SampleRate = audioStream.SampleRate,
+ Type = MediaStreamType.Audio,
+ Index = streams.Count
+ };
+
+ var bitrate = Convert.ToInt32(audioStream.BitRate);
+
+ if (bitrate > 0)
+ {
+ stream.BitRate = bitrate;
+ }
+
+ if (audioStream.LFE > 0)
+ {
+ stream.Channels = audioStream.ChannelCount + 1;
+ }
+
+ streams.Add(stream);
+ }
+
+ /// <summary>
+ /// Adds the subtitle stream.
+ /// </summary>
+ /// <param name="streams">The streams.</param>
+ /// <param name="textStream">The text stream.</param>
+ private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream)
+ {
+ streams.Add(new MediaStream
+ {
+ Language = textStream.LanguageCode,
+ Codec = textStream.CodecShortName,
+ Type = MediaStreamType.Subtitle,
+ Index = streams.Count
+ });
+ }
+
+ /// <summary>
+ /// Adds the subtitle stream.
+ /// </summary>
+ /// <param name="streams">The streams.</param>
+ /// <param name="textStream">The text stream.</param>
+ private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream)
+ {
+ streams.Add(new MediaStream
+ {
+ Language = textStream.LanguageCode,
+ Codec = textStream.CodecShortName,
+ Type = MediaStreamType.Subtitle,
+ Index = streams.Count
+ });
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
new file mode 100644
index 000000000..9e7a1d50a
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
@@ -0,0 +1,68 @@
+using System.IO;
+using MediaBrowser.Model.IO;
+
+namespace MediaBrowser.MediaEncoding.BdInfo;
+
+/// <summary>
+/// Class BdInfoFileInfo.
+/// </summary>
+public class BdInfoFileInfo : BDInfo.IO.IFileInfo
+{
+ private FileSystemMetadata _impl;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BdInfoFileInfo" /> class.
+ /// </summary>
+ /// <param name="impl">The <see cref="FileSystemMetadata" />.</param>
+ public BdInfoFileInfo(FileSystemMetadata impl)
+ {
+ _impl = impl;
+ }
+
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ public string Name => _impl.Name;
+
+ /// <summary>
+ /// Gets the full name.
+ /// </summary>
+ public string FullName => _impl.FullName;
+
+ /// <summary>
+ /// Gets the extension.
+ /// </summary>
+ public string Extension => _impl.Extension;
+
+ /// <summary>
+ /// Gets the length.
+ /// </summary>
+ public long Length => _impl.Length;
+
+ /// <summary>
+ /// Gets a value indicating whether this is a directory.
+ /// </summary>
+ public bool IsDir => _impl.IsDirectory;
+
+ /// <summary>
+ /// Gets a file as file stream.
+ /// </summary>
+ /// <returns>A <see cref="FileStream" /> for the file.</returns>
+ public Stream OpenRead()
+ {
+ return new FileStream(
+ FullName,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.Read);
+ }
+
+ /// <summary>
+ /// Gets a files's content with a stream reader.
+ /// </summary>
+ /// <returns>A <see cref="StreamReader" /> for the file's content.</returns>
+ public StreamReader OpenText()
+ {
+ return new StreamReader(OpenRead());
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 8479b7d50..e1a0e8d67 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -25,11 +25,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg2video",
"mpeg4",
"msmpeg4",
- "dts",
+ "dca",
"ac3",
"aac",
"mp3",
"flac",
+ "truehd",
"h264_qsv",
"hevc_qsv",
"mpeg2_qsv",
@@ -51,26 +52,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
"libx264",
"libx265",
+ "libsvtav1",
"mpeg4",
"msmpeg4",
"libvpx",
"libvpx-vp9",
"aac",
+ "aac_at",
"libfdk_aac",
"ac3",
+ "dca",
"libmp3lame",
"libopus",
"libvorbis",
"flac",
+ "truehd",
"srt",
"h264_amf",
"hevc_amf",
+ "av1_amf",
"h264_qsv",
"hevc_qsv",
+ "av1_qsv",
"h264_nvenc",
"hevc_nvenc",
+ "av1_nvenc",
"h264_vaapi",
"hevc_vaapi",
+ "av1_vaapi",
"h264_v4l2m2m",
"h264_videotoolbox",
"hevc_videotoolbox"
@@ -106,7 +115,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
// vulkan
"libplacebo",
"scale_vulkan",
- "overlay_vulkan"
+ "overlay_vulkan",
+ "hwupload_vaapi",
+ // videotoolbox
+ "yadif_videotoolbox"
};
private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
@@ -210,12 +222,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
- else if (version < MinVersion) // Version is below what we recommend
+
+ if (version < MinVersion) // Version is below what we recommend
{
_logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
return false;
}
- else if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
+
+ if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
{
_logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
return false;
@@ -273,7 +287,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (match.Success)
{
- if (Version.TryParse(match.Groups[1].Value, out var result))
+ if (Version.TryParse(match.Groups[1].ValueSpan, out var result))
{
return result;
}
@@ -323,8 +337,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
RegexOptions.Multiline))
{
var version = new Version(
- int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture),
- int.Parse(match.Groups["minor"].Value, CultureInfo.InvariantCulture));
+ int.Parse(match.Groups["major"].ValueSpan, CultureInfo.InvariantCulture),
+ int.Parse(match.Groups["minor"].ValueSpan, CultureInfo.InvariantCulture));
map.Add(match.Groups["name"].Value, version);
}
@@ -484,7 +498,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
var found = Regex
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
- .Cast<Match>()
.Select(x => x.Groups["codec"].Value)
.Where(x => required.Contains(x));
@@ -513,7 +526,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
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));
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index d0ea0429b..04128c911 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -1,7 +1,9 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -15,21 +17,38 @@ namespace MediaBrowser.MediaEncoding.Encoder
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFile);
}
- return GetConcatInputArgument(inputFile, inputPrefix);
+ return GetFileInputArgument(inputFile, inputPrefix);
+ }
+
+ public static string GetInputArgument(string inputPrefix, IReadOnlyList<string> inputFiles, MediaProtocol protocol)
+ {
+ if (protocol != MediaProtocol.File)
+ {
+ return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", inputFiles[0]);
+ }
+
+ return GetConcatInputArgument(inputFiles, inputPrefix);
}
/// <summary>
/// Gets the concat input argument.
/// </summary>
- /// <param name="inputFile">The input file.</param>
+ /// <param name="inputFiles">The input files.</param>
/// <param name="inputPrefix">The input prefix.</param>
/// <returns>System.String.</returns>
- private static string GetConcatInputArgument(string inputFile, string inputPrefix)
+ private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles, string inputPrefix)
{
// Get all streams
// If there's more than one we'll need to use the concat command
+ if (inputFiles.Count > 1)
+ {
+ var files = string.Join("|", inputFiles.Select(NormalizePath));
+
+ return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files);
+ }
+
// Determine the input path for video files
- return GetFileInputArgument(inputFile, inputPrefix);
+ return GetFileInputArgument(inputFiles[0], inputPrefix);
}
/// <summary>
@@ -56,7 +75,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- private static string NormalizePath(string path)
+ public 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("\"", "\\\"", StringComparison.Ordinal);
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index d2240b5af..4e63d205c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -11,7 +11,9 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -50,11 +52,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
+ private readonly IBlurayExaminer _blurayExaminer;
private readonly IConfiguration _config;
private readonly IServerConfigurationManager _serverConfig;
private readonly string _startupOptionFFmpegPath;
- private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
+ private readonly SemaphoreSlim _thumbnailResourcePool;
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
@@ -94,6 +97,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
ILogger<MediaEncoder> logger,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
+ IBlurayExaminer blurayExaminer,
ILocalizationManager localization,
IConfiguration config,
IServerConfigurationManager serverConfig)
@@ -101,11 +105,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
+ _blurayExaminer = blurayExaminer;
_localization = localization;
_config = config;
_serverConfig = serverConfig;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
- _jsonSerializerOptions = JsonDefaults.Options;
+
+ _jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
+
+ var semaphoreCount = 2 * Environment.ProcessorCount;
+ _thumbnailResourcePool = new SemaphoreSlim(semaphoreCount, semaphoreCount);
}
/// <inheritdoc />
@@ -114,16 +124,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string ProbePath => _ffprobePath;
+ /// <inheritdoc />
public Version EncoderVersion => _ffmpegVersion;
+ /// <inheritdoc />
public bool IsPkeyPauseSupported => _isPkeyPauseSupported;
+ /// <inheritdoc />
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
+ /// <inheritdoc />
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
+ /// <inheritdoc />
public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965;
+ /// <inheritdoc />
public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier;
/// <summary>
@@ -341,26 +357,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
_ffmpegVersion = validator.GetFFmpegVersion();
}
+ /// <inheritdoc />
public bool SupportsEncoder(string encoder)
{
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
}
+ /// <inheritdoc />
public bool SupportsDecoder(string decoder)
{
return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
}
+ /// <inheritdoc />
public bool SupportsHwaccel(string hwaccel)
{
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
}
+ /// <inheritdoc />
public bool SupportsFilter(string filter)
{
return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
}
+ /// <inheritdoc />
public bool SupportsFilterWithOption(FilterOptionType option)
{
if (_filtersWithOption.TryGetValue((int)option, out var val))
@@ -391,24 +412,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
return true;
}
- /// <summary>
- /// Gets the media info.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
+ /// <inheritdoc />
public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
{
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
- var inputFile = request.MediaSource.Path;
-
string analyzeDuration = string.Empty;
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (request.MediaSource.AnalyzeDurationMs > 0)
{
- analyzeDuration = "-analyzeduration " +
- (request.MediaSource.AnalyzeDurationMs * 1000).ToString();
+ analyzeDuration = "-analyzeduration " + (request.MediaSource.AnalyzeDurationMs * 1000).ToString();
}
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
@@ -416,7 +429,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
return GetMediaInfoInternal(
- GetInputArgument(inputFile, request.MediaSource),
+ GetInputArgument(request.MediaSource.Path, request.MediaSource),
request.MediaSource.Path,
request.MediaSource.Protocol,
extractChapters,
@@ -426,36 +439,30 @@ namespace MediaBrowser.MediaEncoding.Encoder
cancellationToken);
}
- /// <summary>
- /// Gets the input argument.
- /// </summary>
- /// <param name="inputFile">The input file.</param>
- /// <param name="mediaSource">The mediaSource.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentException">Unrecognized InputType.</exception>
+ /// <inheritdoc />
+ public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource)
+ {
+ return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol);
+ }
+
+ /// <inheritdoc />
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
{
var prefix = "file";
- if (mediaSource.VideoType == VideoType.BluRay
- || mediaSource.IsoType == IsoType.BluRay)
+ if (mediaSource.IsoType == IsoType.BluRay)
{
prefix = "bluray";
}
- return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
+ return EncodingUtils.GetInputArgument(prefix, new[] { inputFile }, mediaSource.Protocol);
}
- /// <summary>
- /// Gets the input argument for an external subtitle file.
- /// </summary>
- /// <param name="inputFile">The input file.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentException">Unrecognized InputType.</exception>
+ /// <inheritdoc />
public string GetExternalSubtitleInputArgument(string inputFile)
{
const string Prefix = "file";
- return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
+ return EncodingUtils.GetInputArgument(Prefix, new[] { inputFile }, MediaProtocol.File);
}
/// <summary>
@@ -546,6 +553,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ /// <inheritdoc />
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
{
var mediaSource = new MediaSourceInfo
@@ -556,11 +564,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken);
}
+ /// <inheritdoc />
public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken);
}
+ /// <inheritdoc />
public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken)
{
return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken);
@@ -764,6 +774,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ /// <inheritdoc />
public string GetTimeParameter(long ticks)
{
var time = TimeSpan.FromTicks(ticks);
@@ -862,6 +873,114 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new NotImplementedException();
}
+ /// <inheritdoc />
+ public IReadOnlyList<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber)
+ {
+ // Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title .vob files ending with _0.VOB
+ var allVobs = _fileSystem.GetFiles(path, true)
+ .Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase))
+ .Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase))
+ .Where(file => !file.Name.EndsWith("_0.VOB", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(i => i.FullName)
+ .ToList();
+
+ if (titleNumber.HasValue)
+ {
+ var prefix = string.Format(CultureInfo.InvariantCulture, "VTS_{0:D2}_", titleNumber.Value);
+ var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
+
+ if (vobs.Count > 0)
+ {
+ return vobs.Select(i => i.FullName).ToList();
+ }
+
+ _logger.LogWarning("Could not determine .vob files for title {Title} of {Path}.", titleNumber, path);
+ }
+
+ // Check for multiple big titles (> 900 MB)
+ var titles = allVobs
+ .Where(vob => vob.Length >= 900 * 1024 * 1024)
+ .Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString())
+ .Distinct()
+ .ToList();
+
+ // Fall back to first title if no big title is found
+ if (titles.Count == 0)
+ {
+ titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).AsSpan().RightPart('_').ToString());
+ }
+
+ // Aggregate all .vob files of the titles
+ return allVobs
+ .Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).AsSpan().RightPart('_').ToString()))
+ .Select(i => i.FullName)
+ .ToList();
+ }
+
+ /// <inheritdoc />
+ public IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path)
+ {
+ // Get all playable .m2ts files
+ var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files;
+
+ // Get all files from the BDMV/STREAMING directory
+ var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM"));
+
+ // Only return playable local .m2ts files
+ return directoryFiles
+ .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase))
+ .Select(f => f.FullName)
+ .ToList();
+ }
+
+ /// <inheritdoc />
+ public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath)
+ {
+ // Get all playable files
+ IReadOnlyList<string> files;
+ var videoType = source.VideoType;
+ if (videoType == VideoType.Dvd)
+ {
+ files = GetPrimaryPlaylistVobFiles(source.Path, null);
+ }
+ else if (videoType == VideoType.BluRay)
+ {
+ files = GetPrimaryPlaylistM2tsFiles(source.Path);
+ }
+ else
+ {
+ return;
+ }
+
+ // Generate concat configuration entries for each file and write to file
+ using (StreamWriter sw = new StreamWriter(concatFilePath))
+ {
+ foreach (var path in files)
+ {
+ var mediaInfoResult = GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaType = DlnaProfileType.Video,
+ MediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = MediaProtocol.File,
+ VideoType = videoType
+ }
+ },
+ CancellationToken.None).GetAwaiter().GetResult();
+
+ var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
+
+ // Add file path stanza to concat configuration
+ sw.WriteLine("file '{0}'", path);
+
+ // Add duration stanza to concat configuration
+ sw.WriteLine("duration {0}", duration);
+ }
+ }
+ }
+
public bool CanExtractSubtitles(string codec)
{
// TODO is there ever a case when a subtitle can't be extracted??
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 93177298f..a0624fe76 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -22,21 +22,22 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="libse" Version="3.6.10" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
- <PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
- <PackageReference Include="UTF.Unknown" Version="2.5.1" />
+ <PackageReference Include="BDInfo" />
+ <PackageReference Include="libse" />
+ <PackageReference Include="Microsoft.Extensions.Http" />
+ <PackageReference Include="System.Text.Encoding.CodePages" />
+ <PackageReference Include="UTF.Unknown" />
</ItemGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
+ <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
</ItemGroup>
</Project>
diff --git a/MediaBrowser.MediaEncoding/Probing/CodecType.cs b/MediaBrowser.MediaEncoding/Probing/CodecType.cs
new file mode 100644
index 000000000..d7c68e5f3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/CodecType.cs
@@ -0,0 +1,32 @@
+namespace MediaBrowser.MediaEncoding.Probing;
+
+/// <summary>
+/// FFmpeg Codec Type.
+/// </summary>
+public enum CodecType
+{
+ /// <summary>
+ /// Video.
+ /// </summary>
+ Video,
+
+ /// <summary>
+ /// Audio.
+ /// </summary>
+ Audio,
+
+ /// <summary>
+ /// Opaque data information usually continuous.
+ /// </summary>
+ Data,
+
+ /// <summary>
+ /// Subtitles.
+ /// </summary>
+ Subtitle,
+
+ /// <summary>
+ /// Opaque data information usually sparse.
+ /// </summary>
+ Attachment
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index eab8f79bb..294442324 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The codec_type.</value>
[JsonPropertyName("codec_type")]
- public string CodecType { get; set; }
+ public CodecType CodecType { get; set; }
/// <summary>
/// Gets or sets the sample_rate.
@@ -228,11 +228,11 @@ namespace MediaBrowser.MediaEncoding.Probing
public long StartPts { get; set; }
/// <summary>
- /// Gets or sets the is_avc.
+ /// Gets or sets a value indicating whether the stream is AVC.
/// </summary>
/// <value>The is_avc.</value>
[JsonPropertyName("is_avc")]
- public string IsAvc { get; set; }
+ public bool IsAvc { get; set; }
/// <summary>
/// Gets or sets the nal_length_size.
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index c667f5f57..7d655240b 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@@ -67,6 +68,9 @@ namespace MediaBrowser.MediaEncoding.Probing
"諭吉佳作/men",
"//dARTH nULL",
"Phantom/Ghost",
+ "She/Her/Hers",
+ "5/8erl in Ehr'n",
+ "Smith/Kotzen",
};
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
@@ -97,19 +101,16 @@ namespace MediaBrowser.MediaEncoding.Probing
{
info.Container = NormalizeFormat(data.Format.FormatName);
- if (!string.IsNullOrEmpty(data.Format.BitRate))
+ if (int.TryParse(data.Format.BitRate, CultureInfo.InvariantCulture, out var value))
{
- if (int.TryParse(data.Format.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
- {
- info.Bitrate = value;
- }
+ info.Bitrate = value;
}
}
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- var tagStreamType = isAudio ? "audio" : "video";
+ var tagStreamType = isAudio ? CodecType.Audio : CodecType.Video;
- var tagStream = data.Streams?.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase));
+ var tagStream = data.Streams?.FirstOrDefault(i => i.CodecType == tagStreamType);
if (tagStream?.Tags is not null)
{
@@ -251,12 +252,23 @@ namespace MediaBrowser.MediaEncoding.Probing
return null;
}
+ // Handle MPEG-1 container
if (string.Equals(format, "mpegvideo", StringComparison.OrdinalIgnoreCase))
{
return "mpeg";
}
- format = format.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
+ // Handle MPEG-2 container
+ if (string.Equals(format, "mpeg", StringComparison.OrdinalIgnoreCase))
+ {
+ return "ts";
+ }
+
+ // Handle matroska container
+ if (string.Equals(format, "matroska", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mkv";
+ }
return format;
}
@@ -499,7 +511,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson
{
Name = pair.Value,
- Type = PersonType.Writer
+ Type = PersonKind.Writer
});
}
}
@@ -510,7 +522,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson
{
Name = pair.Value,
- Type = PersonType.Producer
+ Type = PersonKind.Producer
});
}
}
@@ -521,7 +533,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson
{
Name = pair.Value,
- Type = PersonType.Director
+ Type = PersonKind.Director
});
}
}
@@ -561,8 +573,8 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- if (string.IsNullOrWhiteSpace(name) ||
- string.IsNullOrWhiteSpace(value))
+ if (string.IsNullOrWhiteSpace(name)
+ || string.IsNullOrWhiteSpace(value))
{
return null;
}
@@ -599,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>MediaAttachments.</returns>
private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
{
- if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)
+ if (streamInfo.CodecType != CodecType.Attachment
&& streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1)
{
return null;
@@ -651,20 +663,10 @@ namespace MediaBrowser.MediaEncoding.Probing
PixelFormat = streamInfo.PixelFormat,
NalLengthSize = streamInfo.NalLengthSize,
TimeBase = streamInfo.TimeBase,
- CodecTimeBase = streamInfo.CodecTimeBase
+ CodecTimeBase = streamInfo.CodecTimeBase,
+ IsAVC = streamInfo.IsAvc
};
- if (string.Equals(streamInfo.IsAvc, "true", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.IsAvc, "1", StringComparison.OrdinalIgnoreCase))
- {
- stream.IsAVC = true;
- }
- else if (string.Equals(streamInfo.IsAvc, "false", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.IsAvc, "0", StringComparison.OrdinalIgnoreCase))
- {
- stream.IsAVC = false;
- }
-
// Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && !streamInfo.CodecTagString.Contains("[0]", StringComparison.OrdinalIgnoreCase))
{
@@ -678,18 +680,15 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.Title = GetDictionaryValue(streamInfo.Tags, "title");
}
- if (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase))
+ if (streamInfo.CodecType == CodecType.Audio)
{
stream.Type = MediaStreamType.Audio;
stream.Channels = streamInfo.Channels;
- if (!string.IsNullOrEmpty(streamInfo.SampleRate))
+ if (int.TryParse(streamInfo.SampleRate, CultureInfo.InvariantCulture, out var sampleRate))
{
- if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
- {
- stream.SampleRate = value;
- }
+ stream.SampleRate = sampleRate;
}
stream.ChannelLayout = ParseChannelLayout(streamInfo.ChannelLayout);
@@ -713,7 +712,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "subtitle", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Subtitle)
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
@@ -733,7 +732,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Video)
{
stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate);
stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate);
@@ -854,35 +853,30 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "data", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Data)
{
stream.Type = MediaStreamType.Data;
}
else
{
- _logger.LogError("Codec Type {CodecType} unknown. The stream (index: {Index}) will be ignored. Warning: Subsequential streams will have a wrong stream specifier!", streamInfo.CodecType, streamInfo.Index);
return null;
}
// Get stream bitrate
var bitrate = 0;
- if (!string.IsNullOrEmpty(streamInfo.BitRate))
+ if (int.TryParse(streamInfo.BitRate, CultureInfo.InvariantCulture, out var value))
{
- if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
- {
- bitrate = value;
- }
+ bitrate = value;
}
// The bitrate info of FLAC musics and some videos is included in formatInfo.
if (bitrate == 0
&& formatInfo is not null
- && !string.IsNullOrEmpty(formatInfo.BitRate)
&& (stream.Type == MediaStreamType.Video || (isAudio && stream.Type == MediaStreamType.Audio)))
{
// If the stream info doesn't have a bitrate get the value from the media format info
- if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
+ if (int.TryParse(formatInfo.BitRate, CultureInfo.InvariantCulture, out value))
{
bitrate = value;
}
@@ -895,29 +889,26 @@ namespace MediaBrowser.MediaEncoding.Probing
// Extract bitrate info from tag "BPS" if possible.
if (!stream.BitRate.HasValue
- && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase)
- || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)))
+ && (streamInfo.CodecType == CodecType.Audio
+ || streamInfo.CodecType == CodecType.Video))
{
var bps = GetBPSFromTags(streamInfo);
if (bps > 0)
{
stream.BitRate = bps;
}
- }
-
- // Get average bitrate info from tag "NUMBER_OF_BYTES" and "DURATION" if possible.
- if (!stream.BitRate.HasValue
- && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase)
- || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)))
- {
- var durationInSeconds = GetRuntimeSecondsFromTags(streamInfo);
- var bytes = GetNumberOfBytesFromTags(streamInfo);
- if (durationInSeconds is not null && bytes is not null)
+ else
{
- var bps = Convert.ToInt32(bytes * 8 / durationInSeconds, CultureInfo.InvariantCulture);
- if (bps > 0)
+ // Get average bitrate info from tag "NUMBER_OF_BYTES" and "DURATION" if possible.
+ var durationInSeconds = GetRuntimeSecondsFromTags(streamInfo);
+ var bytes = GetNumberOfBytesFromTags(streamInfo);
+ if (durationInSeconds is not null && bytes is not null)
{
- stream.BitRate = bps;
+ bps = Convert.ToInt32(bytes * 8 / durationInSeconds, CultureInfo.InvariantCulture);
+ if (bps > 0)
+ {
+ stream.BitRate = bps;
+ }
}
}
}
@@ -948,12 +939,8 @@ namespace MediaBrowser.MediaEncoding.Probing
private void NormalizeStreamTitle(MediaStream stream)
{
- if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase))
- {
- stream.Title = null;
- }
-
- if (stream.Type == MediaStreamType.EmbeddedImage)
+ if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase)
+ || stream.Type == MediaStreamType.EmbeddedImage)
{
stream.Title = null;
}
@@ -984,7 +971,7 @@ namespace MediaBrowser.MediaEncoding.Probing
return null;
}
- return input.Split('(').FirstOrDefault();
+ return input.AsSpan().LeftPart('(').ToString();
}
private string GetAspectRatio(MediaStreamInfo info)
@@ -992,11 +979,11 @@ namespace MediaBrowser.MediaEncoding.Probing
var original = info.DisplayAspectRatio;
var parts = (original ?? string.Empty).Split(':');
- if (!(parts.Length == 2 &&
- int.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var width) &&
- int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var height) &&
- width > 0 &&
- height > 0))
+ if (!(parts.Length == 2
+ && int.TryParse(parts[0], CultureInfo.InvariantCulture, out var width)
+ && int.TryParse(parts[1], CultureInfo.InvariantCulture, out var height)
+ && width > 0
+ && height > 0))
{
width = info.Width;
height = info.Height;
@@ -1077,12 +1064,6 @@ namespace MediaBrowser.MediaEncoding.Probing
int index = value.IndexOf('/');
if (index == -1)
{
- // REVIEW: is this branch actually required? (i.e. does ffprobe ever output something other than a fraction?)
- if (float.TryParse(value, NumberStyles.AllowThousands | NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
-
return null;
}
@@ -1098,7 +1079,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data)
{
// Get the first info stream
- var stream = result.Streams?.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase));
+ var stream = result.Streams?.FirstOrDefault(s => s.CodecType == CodecType.Audio);
if (stream is null)
{
return;
@@ -1128,8 +1109,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
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))
+ if (int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps))
{
return parsedBps;
}
@@ -1145,7 +1125,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
var duration = GetDictionaryValue(streamInfo.Tags, "DURATION-eng") ?? GetDictionaryValue(streamInfo.Tags, "DURATION");
- if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var parsedDuration))
+ if (TimeSpan.TryParse(duration, out var parsedDuration))
{
return parsedDuration.TotalSeconds;
}
@@ -1162,8 +1142,7 @@ namespace MediaBrowser.MediaEncoding.Probing
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))
+ if (long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes))
{
return parsedBytes;
}
@@ -1188,7 +1167,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(composer, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Composer });
}
}
@@ -1196,7 +1175,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(conductor, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Conductor });
}
}
@@ -1204,7 +1183,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(lyricist, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Lyricist });
}
}
@@ -1220,7 +1199,7 @@ namespace MediaBrowser.MediaEncoding.Probing
people.Add(new BaseItemPerson
{
Name = match.Groups["name"].Value,
- Type = PersonType.Actor,
+ Type = PersonKind.Actor,
Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value)
});
}
@@ -1232,7 +1211,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(writer, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Writer });
}
}
@@ -1240,7 +1219,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(arranger, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Arranger });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Arranger });
}
}
@@ -1248,7 +1227,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(engineer, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Engineer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Engineer });
}
}
@@ -1256,7 +1235,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(mixer, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Mixer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Mixer });
}
}
@@ -1264,7 +1243,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
foreach (var person in Split(remixer, false))
{
- people.Add(new BaseItemPerson { Name = person, Type = PersonType.Remixer });
+ people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Remixer });
}
}
@@ -1455,7 +1434,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
var disc = tags.GetValueOrDefault(tagName);
- if (!string.IsNullOrEmpty(disc) && int.TryParse(disc.AsSpan().LeftPart('/'), out var discNum))
+ if (int.TryParse(disc.AsSpan().LeftPart('/'), out var discNum))
{
return discNum;
}
@@ -1475,7 +1454,7 @@ namespace MediaBrowser.MediaEncoding.Probing
// Limit accuracy to milliseconds to match xml saving
var secondsString = chapter.StartTime;
- if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
+ if (double.TryParse(secondsString, CultureInfo.InvariantCulture, out var seconds))
{
var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds);
info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
@@ -1516,7 +1495,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
- .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor })
+ .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonKind.Actor })
.ToArray();
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 90bc49132..794906c3b 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -449,7 +449,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
try
{
- _logger.LogInformation("Deleting converted subtitle due to failure: ", outputPath);
+ _logger.LogInformation("Deleting converted subtitle due to failure: {Path}", outputPath);
_fileSystem.DeleteFile(outputPath);
}
catch (IOException ex)
@@ -624,10 +624,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new FfmpegException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath));
}
- else
- {
- _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
- }
+
+ _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase))
{