aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-02-14 12:07:30 +0100
committerGitHub <noreply@github.com>2026-02-14 12:07:30 +0100
commit29582ed461b693368ec56567c2e40cfa20ef4bf5 (patch)
tree04721b833e8e6108c2e13c4f0ea9f4dc7b2ae946 /MediaBrowser.MediaEncoding
parentca6d499680f9fbb369844a11eb0e0213b66bb00b (diff)
parent3b6985986709473c69ba785460c702c6bbe3771d (diff)
Merge branch 'master' into issue15137
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs12
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj3
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs33
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs94
-rw-r--r--MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs4
6 files changed, 99 insertions, 54 deletions
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
index 7c0be5a9f..dc20a6d63 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using System.Linq;
using BDInfo.IO;
@@ -58,6 +59,8 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
}
}
+ private static bool IsHidden(ReadOnlySpan<char> name) => name.StartsWith('.');
+
/// <summary>
/// Gets the directories.
/// </summary>
@@ -65,6 +68,7 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
public IDirectoryInfo[] GetDirectories()
{
return _fileSystem.GetDirectories(_impl.FullName)
+ .Where(d => !IsHidden(d.Name))
.Select(x => new BdInfoDirectoryInfo(_fileSystem, x))
.ToArray();
}
@@ -76,6 +80,7 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
public IFileInfo[] GetFiles()
{
return _fileSystem.GetFiles(_impl.FullName)
+ .Where(d => !IsHidden(d.Name))
.Select(x => new BdInfoFileInfo(x))
.ToArray();
}
@@ -88,6 +93,7 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
public IFileInfo[] GetFiles(string searchPattern)
{
return _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false)
+ .Where(d => !IsHidden(d.Name))
.Select(x => new BdInfoFileInfo(x))
.ToArray();
}
@@ -105,6 +111,7 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
new[] { searchPattern },
false,
searchOption == SearchOption.AllDirectories)
+ .Where(d => !IsHidden(d.Name))
.Select(x => new BdInfoFileInfo(x))
.ToArray();
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 8350d1613..73c5b88c8 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -511,7 +511,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
? "{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";
- if (!isAudio && _proberSupportsFirstVideoFrame)
+ if (protocol == MediaProtocol.File && !isAudio && _proberSupportsFirstVideoFrame)
{
args += " -show_frames -only_first_vframe";
}
@@ -1122,7 +1122,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void StartProcess(ProcessWrapper process)
{
process.Process.Start();
- process.Process.PriorityClass = ProcessPriorityClass.BelowNormal;
+
+ try
+ {
+ process.Process.PriorityClass = ProcessPriorityClass.BelowNormal;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Unable to set process priority to BelowNormal for {ProcessFileName}", process.Process.StartInfo.FileName);
+ }
lock (_runningProcessesLock)
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index be7eeda92..fc11047a7 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -26,7 +26,6 @@
<PackageReference Include="BDInfo" />
<PackageReference Include="libse" />
<PackageReference Include="Microsoft.Extensions.Http" />
- <PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="UTF.Unknown" />
</ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 00a9ae797..dbe532289 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -83,6 +83,7 @@ namespace MediaBrowser.MediaEncoding.Probing
"Smith/Kotzen",
"We;Na",
"LSR/CITY",
+ "Kairon; IRSE!",
};
/// <summary>
@@ -154,11 +155,12 @@ namespace MediaBrowser.MediaEncoding.Probing
info.Name = tags.GetFirstNotNullNorWhiteSpaceValue("title", "title-eng");
info.ForcedSortName = tags.GetFirstNotNullNorWhiteSpaceValue("sort_name", "title-sort", "titlesort");
- info.Overview = tags.GetFirstNotNullNorWhiteSpaceValue("synopsis", "description", "desc");
+ info.Overview = tags.GetFirstNotNullNorWhiteSpaceValue("synopsis", "description", "desc", "comment");
- info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort");
info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number");
- info.ShowName = tags.GetValueOrDefault("show_name");
+ info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort") ??
+ FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_id");
+ info.ShowName = tags.GetValueOrDefault("show_name", "show");
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retail/premiere date
@@ -299,9 +301,12 @@ namespace MediaBrowser.MediaEncoding.Probing
// Handle WebM
else if (string.Equals(splitFormat[i], "webm", StringComparison.OrdinalIgnoreCase))
{
- // Limit WebM to supported codecs
- if (mediaStreams.Any(stream => (stream.Type == MediaStreamType.Video && !_webmVideoCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))
- || (stream.Type == MediaStreamType.Audio && !_webmAudioCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))))
+ // Limit WebM to supported stream types and codecs.
+ // FFprobe can report "matroska,webm" for Matroska-like containers, so only keep "webm" if all streams are WebM-compatible.
+ // Any stream that is not video nor audio is not supported in WebM and should disqualify the webm container probe result.
+ if (mediaStreams.Any(stream => stream.Type is not MediaStreamType.Video and not MediaStreamType.Audio)
+ || mediaStreams.Any(stream => (stream.Type == MediaStreamType.Video && !_webmVideoCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))
+ || (stream.Type == MediaStreamType.Audio && !_webmAudioCodecs.Contains(stream.Codec, StringComparison.OrdinalIgnoreCase))))
{
splitFormat[i] = string.Empty;
}
@@ -853,7 +858,12 @@ namespace MediaBrowser.MediaEncoding.Probing
}
// http://stackoverflow.com/questions/17353387/how-to-detect-anamorphic-video-with-ffprobe
- if (string.Equals(streamInfo.SampleAspectRatio, "1:1", StringComparison.Ordinal))
+ if (string.IsNullOrEmpty(streamInfo.SampleAspectRatio)
+ && string.IsNullOrEmpty(streamInfo.DisplayAspectRatio))
+ {
+ stream.IsAnamorphic = false;
+ }
+ else if (string.Equals(streamInfo.SampleAspectRatio, "1:1", StringComparison.Ordinal))
{
stream.IsAnamorphic = false;
}
@@ -930,6 +940,15 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Rotation = data.Rotation;
}
+
+ // Parse video frame cropping metadata from side_data
+ // TODO: save them and make HW filters to apply them in HWA pipelines
+ else if (string.Equals(data.SideDataType, "Frame Cropping", StringComparison.OrdinalIgnoreCase))
+ {
+ // Streams containing artificially added frame cropping
+ // metadata should not be marked as anamorphic.
+ stream.IsAnamorphic = false;
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 88a7bb4b4..bf7ec05a9 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -13,8 +13,10 @@ using System.Threading;
using System.Threading.Tasks;
using AsyncKeyedLock;
using MediaBrowser.Common;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
@@ -37,6 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ISubtitleParser _subtitleParser;
private readonly IPathManager _pathManager;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary>
/// The _semaphoreLocks.
@@ -54,7 +57,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager,
ISubtitleParser subtitleParser,
- IPathManager pathManager)
+ IPathManager pathManager,
+ IServerConfigurationManager serverConfigurationManager)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -63,6 +67,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_mediaSourceManager = mediaSourceManager;
_subtitleParser = subtitleParser;
_pathManager = pathManager;
+ _serverConfigurationManager = serverConfigurationManager;
}
private MemoryStream ConvertSubtitles(
@@ -167,23 +172,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private async Task<Stream> GetSubtitleStream(SubtitleInfo fileInfo, CancellationToken cancellationToken)
{
- if (fileInfo.IsExternal)
+ if (fileInfo.Protocol == MediaProtocol.Http)
{
- var stream = await GetStream(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
+ var result = await DetectCharset(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false);
+ var detected = result.Detected;
+
+ if (detected is not null)
{
- var result = await CharsetDetector.DetectFromStreamAsync(stream, cancellationToken).ConfigureAwait(false);
- var detected = result.Detected;
- stream.Position = 0;
+ _logger.LogDebug("charset {CharSet} detected for {Path}", detected.EncodingName, fileInfo.Path);
- if (detected is not null)
- {
- _logger.LogDebug("charset {CharSet} detected for {Path}", detected.EncodingName, fileInfo.Path);
+ using var stream = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .GetStreamAsync(new Uri(fileInfo.Path), cancellationToken)
+ .ConfigureAwait(false);
- using var reader = new StreamReader(stream, detected.Encoding);
- var text = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
+ await using (stream.ConfigureAwait(false))
+ {
+ using var reader = new StreamReader(stream, detected.Encoding);
+ var text = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
- return new MemoryStream(Encoding.UTF8.GetBytes(text));
+ return new MemoryStream(Encoding.UTF8.GetBytes(text));
}
}
}
@@ -213,7 +220,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
};
}
- var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
+ var currentFormat = subtitleStream.Codec ?? Path.GetExtension(subtitleStream.Path)
.TrimStart('.');
// Handle PGS subtitles as raw streams for the client to render
@@ -394,7 +401,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
+ var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes;
+ await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false);
exitCode = process.ExitCode;
}
catch (OperationCanceledException)
@@ -677,7 +685,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
+ var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes;
+ await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false);
exitCode = process.ExitCode;
}
catch (OperationCanceledException)
@@ -828,7 +837,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
+ var timeoutMinutes = _serverConfigurationManager.GetEncodingOptions().SubtitleExtractionTimeoutMinutes;
+ await process.WaitForExitAsync(TimeSpan.FromMinutes(timeoutMinutes)).ConfigureAwait(false);
exitCode = process.ExitCode;
}
catch (OperationCanceledException)
@@ -933,42 +943,44 @@ namespace MediaBrowser.MediaEncoding.Subtitles
.ConfigureAwait(false);
}
- var stream = await GetStream(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
- {
- var result = await CharsetDetector.DetectFromStreamAsync(stream, cancellationToken).ConfigureAwait(false);
- var charset = result.Detected?.EncodingName ?? string.Empty;
+ var result = await DetectCharset(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false);
+ var charset = result.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))
- && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
- || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
- {
- charset = 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))
+ && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
+ {
+ charset = string.Empty;
+ }
- _logger.LogDebug("charset {0} detected for {Path}", charset, path);
+ _logger.LogDebug("charset {0} detected for {Path}", charset, path);
- return charset;
- }
+ return charset;
}
- private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+ private async Task<DetectionResult> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
switch (protocol)
{
case MediaProtocol.Http:
- {
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .GetAsync(new Uri(path), cancellationToken)
- .ConfigureAwait(false);
- return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- }
+ {
+ using var stream = await _httpClientFactory
+ .CreateClient(NamedClient.Default)
+ .GetStreamAsync(new Uri(path), cancellationToken)
+ .ConfigureAwait(false);
+
+ return await CharsetDetector.DetectFromStreamAsync(stream, cancellationToken).ConfigureAwait(false);
+ }
case MediaProtocol.File:
- return AsyncFile.OpenRead(path);
+ {
+ return await CharsetDetector.DetectFromFileAsync(path, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
default:
- throw new ArgumentOutOfRangeException(nameof(protocol));
+ throw new ArgumentOutOfRangeException(nameof(protocol), protocol, "Unsupported protocol");
}
}
diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
index 0cda803d6..defd855ec 100644
--- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
+++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
@@ -396,7 +396,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath);
// If subtitles get burned in fonts may need to be extracted from the media file
- if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+ if (state.SubtitleStream is not null && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode || state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding))
{
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
{
@@ -673,7 +673,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
if (state.VideoRequest is not null)
{
- _encodingHelper.TryStreamCopy(state);
+ _encodingHelper.TryStreamCopy(state, encodingOptions);
}
}