From bc316b3dc855e93d4d11e2c0d73d70326c38b889 Mon Sep 17 00:00:00 2001 From: NoFear0411 <9083405+NoFear0411@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:00:05 +0400 Subject: Fix near-1:1 SAR values falsely flagged as anamorphic Encoders sometimes produce sample aspect ratios like 3201:3200 (0.03% off square) for content that has effectively square pixels. The exact string comparison against "1:1" marks these as anamorphic, which triggers unnecessary transcoding on clients that require non-anamorphic video. Parse the SAR ratio numerically and treat values within 1% of 1:1 as square pixels. This threshold is well clear of the nearest real anamorphic SAR (PAL 4:3 at 16:15 = 6.67% off). --- .../Probing/ProbeResultNormalizer.cs | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index dbe5322897..471df369ba 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -863,7 +863,7 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.IsAnamorphic = false; } - else if (string.Equals(streamInfo.SampleAspectRatio, "1:1", StringComparison.Ordinal)) + else if (IsNearSquarePixelSar(streamInfo.SampleAspectRatio)) { stream.IsAnamorphic = false; } @@ -1154,6 +1154,32 @@ namespace MediaBrowser.MediaEncoding.Probing return Math.Abs(d1 - d2) <= variance; } + /// + /// Determines whether a sample aspect ratio represents square (or near-square) pixels. + /// Some encoders produce SARs like 3201:3200 for content that is effectively 1:1, + /// which would be falsely classified as anamorphic by an exact string comparison. + /// A 1% tolerance safely covers encoder rounding artifacts while preserving detection + /// of genuine anamorphic content (closest standard is PAL 4:3 at 16:15 = 6.67% off). + /// + internal static bool IsNearSquarePixelSar(string sar) + { + if (string.IsNullOrEmpty(sar)) + { + return false; + } + + var parts = sar.Split(':'); + if (parts.Length == 2 + && double.TryParse(parts[0], CultureInfo.InvariantCulture, out var num) + && double.TryParse(parts[1], CultureInfo.InvariantCulture, out var den) + && den > 0) + { + return IsClose(num / den, 1.0, 0.01); + } + + return string.Equals(sar, "1:1", StringComparison.Ordinal); + } + /// /// Gets a frame rate from a string value in ffprobe output /// This could be a number or in the format of 2997/125. -- cgit v1.2.3 From d87fe973f3516e20ae4c4ecd8036286deeb4b51d Mon Sep 17 00:00:00 2001 From: NoFear0411 <9083405+NoFear0411@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:51:27 +0400 Subject: Fix StyleCop and xUnit analyzer errors - Add missing param and returns XML doc tags (SA1611, SA1615) - Remove trailing alignment whitespace in test attributes (SA1025) - Use nullable string parameter for null test case (xUnit1012) --- .../Probing/ProbeResultNormalizer.cs | 2 ++ .../Probing/ProbeResultNormalizerTests.cs | 28 +++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 471df369ba..127bdd380d 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1161,6 +1161,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// A 1% tolerance safely covers encoder rounding artifacts while preserving detection /// of genuine anamorphic content (closest standard is PAL 4:3 at 16:15 = 6.67% off). /// + /// The sample aspect ratio string in "N:D" format. + /// true if the SAR is within 1% of 1:1; otherwise false. internal static bool IsNearSquarePixelSar(string sar) { if (string.IsNullOrEmpty(sar)) diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 40f853699b..8ebbd029ac 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -40,20 +40,20 @@ namespace Jellyfin.MediaEncoding.Tests.Probing => Assert.Equal(expected, ProbeResultNormalizer.GetFrameRate(value)); [Theory] - [InlineData("1:1", true)] // exact square pixels - [InlineData("3201:3200", true)] // 0.03% off — encoder rounding artifact (4K HEVC) - [InlineData("1215:1216", true)] // 0.08% off — encoder rounding artifact - [InlineData("1001:1000", true)] // 0.1% off — encoder rounding artifact - [InlineData("16:15", false)] // 6.67% off — PAL DVD 4:3, genuinely anamorphic - [InlineData("8:9", false)] // 11.1% off — NTSC DVD 4:3 - [InlineData("32:27", false)] // 18.5% off — NTSC DVD 16:9 - [InlineData("10:11", false)] // 9.1% off — DV NTSC - [InlineData("64:45", false)] // 42.2% off — PAL DVD 16:9 - [InlineData("4:3", false)] // 33.3% off — classic anamorphic - [InlineData("0:1", false)] // invalid/unknown SAR - [InlineData("", false)] // empty - [InlineData(null, false)] // null - public void IsNearSquarePixelSar_DetectsCorrectly(string sar, bool expected) + [InlineData("1:1", true)] + [InlineData("3201:3200", true)] + [InlineData("1215:1216", true)] + [InlineData("1001:1000", true)] + [InlineData("16:15", false)] + [InlineData("8:9", false)] + [InlineData("32:27", false)] + [InlineData("10:11", false)] + [InlineData("64:45", false)] + [InlineData("4:3", false)] + [InlineData("0:1", false)] + [InlineData("", false)] + [InlineData(null, false)] + public void IsNearSquarePixelSar_DetectsCorrectly(string? sar, bool expected) => Assert.Equal(expected, ProbeResultNormalizer.IsNearSquarePixelSar(sar)); [Fact] -- cgit v1.2.3 From 3997e016fa092f8fad171ce5f24f619b552e5f78 Mon Sep 17 00:00:00 2001 From: lowbit Date: Fri, 13 Mar 2026 15:33:06 -0400 Subject: Backport pull request #16257 from jellyfin/release-10.11.z Fix subtitle extraction caching empty files Original-merge: 6864e108b8b36ad25655e683b2cf2abf8b8ca346 Merged-by: joshuaboniface Backported-by: Bond_009 --- .../Subtitles/SubtitleEncoder.cs | 52 +++++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index bf7ec05a96..aeaf7f4423 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -328,7 +328,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { - if (!File.Exists(outputPath)) + if (!File.Exists(outputPath) || _fileSystem.GetFileInfo(outputPath).Length == 0) { await ConvertTextSubtitleToSrtInternal(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false); } @@ -431,9 +431,22 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } } - else if (!File.Exists(outputPath)) + else if (!File.Exists(outputPath) || _fileSystem.GetFileInfo(outputPath).Length == 0) { failed = true; + + try + { + _logger.LogWarning("Deleting converted subtitle due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting converted subtitle {Path}", outputPath); + } } if (failed) @@ -507,7 +520,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var releaser = await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false); - if (File.Exists(outputPath)) + if (File.Exists(outputPath) && _fileSystem.GetFileInfo(outputPath).Length > 0) { releaser.Dispose(); continue; @@ -722,10 +735,24 @@ namespace MediaBrowser.MediaEncoding.Subtitles { foreach (var outputPath in outputPaths) { - if (!File.Exists(outputPath)) + if (!File.Exists(outputPath) || _fileSystem.GetFileInfo(outputPath).Length == 0) { _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); failed = true; + + try + { + _logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath); + } + continue; } @@ -764,7 +791,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { - if (!File.Exists(outputPath)) + if (!File.Exists(outputPath) || _fileSystem.GetFileInfo(outputPath).Length == 0) { var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream); @@ -867,9 +894,22 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath); } } - else if (!File.Exists(outputPath)) + else if (!File.Exists(outputPath) || _fileSystem.GetFileInfo(outputPath).Length == 0) { failed = true; + + try + { + _logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath); + } } if (failed) -- cgit v1.2.3 From 31adb5dcd135efd1f790fd9acbdc6b50aee6afe4 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Fri, 13 Mar 2026 15:33:08 -0400 Subject: Backport pull request #16392 from jellyfin/release-10.11.z Fix filter detection in FFmpeg 8.1 Original-merge: 55c00d76bbbe2d2759f33fab673f26b26093a30e Merged-by: Bond-009 Backported-by: Bond_009 --- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f4e8c39c11..68d6d215b2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -693,7 +693,7 @@ namespace MediaBrowser.MediaEncoding.Encoder [GeneratedRegex("^\\s\\S{6}\\s(?[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] private static partial Regex CodecRegex(); - [GeneratedRegex("^\\s\\S{3}\\s(?[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] + [GeneratedRegex("^\\s\\S{2,3}\\s(?[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] private static partial Regex FilterRegex(); } } -- cgit v1.2.3 From 6ea77f484d6c1b3faf160aee41d1ea099e64b85e Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Sun, 29 Mar 2026 12:38:01 +0200 Subject: Fix attachment extraction of files without video or audio stream (#16312) * Fix attachment extraction of files without video or audio stream * Apply review suggestions --- .../Attachments/AttachmentExtractor.cs | 59 +++++++++++++--------- 1 file changed, 35 insertions(+), 24 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 48a0654bb1..f7a1581a76 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -115,7 +115,6 @@ namespace MediaBrowser.MediaEncoding.Attachments await ExtractAllAttachmentsInternal( inputFile, mediaSource, - false, cancellationToken).ConfigureAwait(false); } } @@ -123,7 +122,6 @@ namespace MediaBrowser.MediaEncoding.Attachments private async Task ExtractAllAttachmentsInternal( string inputFile, MediaSourceInfo mediaSource, - bool isExternal, CancellationToken cancellationToken) { var inputPath = _mediaEncoder.GetInputArgument(inputFile, mediaSource); @@ -142,11 +140,19 @@ namespace MediaBrowser.MediaEncoding.Attachments return; } + // Files without video/audio streams (e.g. MKS subtitle files) don't need a dummy + // output since there are no streams to process. Omit "-t 0 -f null null" so ffmpeg + // doesn't fail trying to open an output with no streams. It will exit with code 1 + // ("at least one output file must be specified") which is expected and harmless + // since we only need the -dump_attachment side effect. + var hasVideoOrAudioStream = mediaSource.MediaStreams + .Any(s => s.Type == MediaStreamType.Video || s.Type == MediaStreamType.Audio); var processArgs = string.Format( CultureInfo.InvariantCulture, - "-dump_attachment:t \"\" -y {0} -i {1} -t 0 -f null null", + "-dump_attachment:t \"\" -y {0} -i {1} {2}", inputPath.EndsWith(".concat\"", StringComparison.OrdinalIgnoreCase) ? "-f concat -safe 0" : string.Empty, - inputPath); + inputPath, + hasVideoOrAudioStream ? "-t 0 -f null null" : string.Empty); int exitCode; @@ -185,12 +191,7 @@ namespace MediaBrowser.MediaEncoding.Attachments if (exitCode != 0) { - if (isExternal && exitCode == 1) - { - // ffmpeg returns exitCode 1 because there is no video or audio stream - // this can be ignored - } - else + if (hasVideoOrAudioStream || exitCode != 1) { failed = true; @@ -205,7 +206,8 @@ namespace MediaBrowser.MediaEncoding.Attachments } } } - else if (!Directory.Exists(outputFolder)) + + if (!failed && !Directory.Exists(outputFolder)) { failed = true; } @@ -246,6 +248,7 @@ namespace MediaBrowser.MediaEncoding.Attachments { await ExtractAttachmentInternal( _mediaEncoder.GetInputArgument(inputFile, mediaSource), + mediaSource, mediaAttachment.Index, attachmentPath, cancellationToken).ConfigureAwait(false); @@ -257,6 +260,7 @@ namespace MediaBrowser.MediaEncoding.Attachments private async Task ExtractAttachmentInternal( string inputPath, + MediaSourceInfo mediaSource, int attachmentStreamIndex, string outputPath, CancellationToken cancellationToken) @@ -267,12 +271,15 @@ namespace MediaBrowser.MediaEncoding.Attachments Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputPath))); + var hasVideoOrAudioStream = mediaSource.MediaStreams + .Any(s => s.Type == MediaStreamType.Video || s.Type == MediaStreamType.Audio); var processArgs = string.Format( CultureInfo.InvariantCulture, - "-dump_attachment:{1} \"{2}\" -i {0} -t 0 -f null null", + "-dump_attachment:{1} \"{2}\" -i {0} {3}", inputPath, attachmentStreamIndex, - EncodingUtils.NormalizePath(outputPath)); + EncodingUtils.NormalizePath(outputPath), + hasVideoOrAudioStream ? "-t 0 -f null null" : string.Empty); int exitCode; @@ -310,22 +317,26 @@ namespace MediaBrowser.MediaEncoding.Attachments if (exitCode != 0) { - failed = true; - - _logger.LogWarning("Deleting extracted attachment {Path} due to failure: {ExitCode}", outputPath, exitCode); - try + if (hasVideoOrAudioStream || exitCode != 1) { - if (File.Exists(outputPath)) + failed = true; + + _logger.LogWarning("Deleting extracted attachment {Path} due to failure: {ExitCode}", outputPath, exitCode); + try { - _fileSystem.DeleteFile(outputPath); + if (File.Exists(outputPath)) + { + _fileSystem.DeleteFile(outputPath); + } + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted attachment {Path}", outputPath); } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting extracted attachment {Path}", outputPath); } } - else if (!File.Exists(outputPath)) + + if (!failed && !File.Exists(outputPath)) { failed = true; } -- cgit v1.2.3 From a6da575785e678e64ed03978d1f4f60a80423121 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 29 Mar 2026 14:16:26 +0200 Subject: Only set IsAvc for video streams Also enables nullable for MediaStreamInfo Makes more properties nullable that aren't always present --- .../Probing/FFProbeHelpers.cs | 4 +- .../Probing/MediaStreamInfo.cs | 82 +++++++++++----------- .../Probing/ProbeResultNormalizer.cs | 23 ++---- MediaBrowser.Model/Entities/MediaStream.cs | 1 - .../Probing/ProbeResultNormalizerTests.cs | 4 +- 5 files changed, 50 insertions(+), 64 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index 6f51e1a6ab..975c2b8161 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -74,9 +74,9 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The dict. /// Dictionary{System.StringSystem.String}. - private static Dictionary ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary dict) + private static Dictionary ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary dict) { - return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); + return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 2944423248..f631c471f6 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Text.Json.Serialization; @@ -22,21 +20,21 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The profile. [JsonPropertyName("profile")] - public string Profile { get; set; } + public string? Profile { get; set; } /// /// Gets or sets the codec_name. /// /// The codec_name. [JsonPropertyName("codec_name")] - public string CodecName { get; set; } + public string? CodecName { get; set; } /// /// Gets or sets the codec_long_name. /// /// The codec_long_name. [JsonPropertyName("codec_long_name")] - public string CodecLongName { get; set; } + public string? CodecLongName { get; set; } /// /// Gets or sets the codec_type. @@ -50,49 +48,49 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The sample_rate. [JsonPropertyName("sample_rate")] - public string SampleRate { get; set; } + public string? SampleRate { get; set; } /// /// Gets or sets the channels. /// /// The channels. [JsonPropertyName("channels")] - public int Channels { get; set; } + public int? Channels { get; set; } /// /// Gets or sets the channel_layout. /// /// The channel_layout. [JsonPropertyName("channel_layout")] - public string ChannelLayout { get; set; } + public string? ChannelLayout { get; set; } /// /// Gets or sets the avg_frame_rate. /// /// The avg_frame_rate. [JsonPropertyName("avg_frame_rate")] - public string AverageFrameRate { get; set; } + public string? AverageFrameRate { get; set; } /// /// Gets or sets the duration. /// /// The duration. [JsonPropertyName("duration")] - public string Duration { get; set; } + public string? Duration { get; set; } /// /// Gets or sets the bit_rate. /// /// The bit_rate. [JsonPropertyName("bit_rate")] - public string BitRate { get; set; } + public string? BitRate { get; set; } /// /// Gets or sets the width. /// /// The width. [JsonPropertyName("width")] - public int Width { get; set; } + public int? Width { get; set; } /// /// Gets or sets the refs. @@ -106,21 +104,21 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The height. [JsonPropertyName("height")] - public int Height { get; set; } + public int? Height { get; set; } /// /// Gets or sets the display_aspect_ratio. /// /// The display_aspect_ratio. [JsonPropertyName("display_aspect_ratio")] - public string DisplayAspectRatio { get; set; } + public string? DisplayAspectRatio { get; set; } /// /// Gets or sets the tags. /// /// The tags. [JsonPropertyName("tags")] - public IReadOnlyDictionary Tags { get; set; } + public IReadOnlyDictionary? Tags { get; set; } /// /// Gets or sets the bits_per_sample. @@ -141,7 +139,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The r_frame_rate. [JsonPropertyName("r_frame_rate")] - public string RFrameRate { get; set; } + public string? RFrameRate { get; set; } /// /// Gets or sets the has_b_frames. @@ -155,70 +153,70 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The sample_aspect_ratio. [JsonPropertyName("sample_aspect_ratio")] - public string SampleAspectRatio { get; set; } + public string? SampleAspectRatio { get; set; } /// /// Gets or sets the pix_fmt. /// /// The pix_fmt. [JsonPropertyName("pix_fmt")] - public string PixelFormat { get; set; } + public string? PixelFormat { get; set; } /// /// Gets or sets the level. /// /// The level. [JsonPropertyName("level")] - public int Level { get; set; } + public int? Level { get; set; } /// /// Gets or sets the time_base. /// /// The time_base. [JsonPropertyName("time_base")] - public string TimeBase { get; set; } + public string? TimeBase { get; set; } /// /// Gets or sets the start_time. /// /// The start_time. [JsonPropertyName("start_time")] - public string StartTime { get; set; } + public string? StartTime { get; set; } /// /// Gets or sets the codec_time_base. /// /// The codec_time_base. [JsonPropertyName("codec_time_base")] - public string CodecTimeBase { get; set; } + public string? CodecTimeBase { get; set; } /// /// Gets or sets the codec_tag. /// /// The codec_tag. [JsonPropertyName("codec_tag")] - public string CodecTag { get; set; } + public string? CodecTag { get; set; } /// - /// Gets or sets the codec_tag_string. + /// Gets or sets the codec_tag_string?. /// - /// The codec_tag_string. - [JsonPropertyName("codec_tag_string")] - public string CodecTagString { get; set; } + /// The codec_tag_string?. + [JsonPropertyName("codec_tag_string?")] + public string? CodecTagString { get; set; } /// /// Gets or sets the sample_fmt. /// /// The sample_fmt. [JsonPropertyName("sample_fmt")] - public string SampleFmt { get; set; } + public string? SampleFmt { get; set; } /// /// Gets or sets the dmix_mode. /// /// The dmix_mode. [JsonPropertyName("dmix_mode")] - public string DmixMode { get; set; } + public string? DmixMode { get; set; } /// /// Gets or sets the start_pts. @@ -232,90 +230,90 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The is_avc. [JsonPropertyName("is_avc")] - public bool IsAvc { get; set; } + public bool? IsAvc { get; set; } /// /// Gets or sets the nal_length_size. /// /// The nal_length_size. [JsonPropertyName("nal_length_size")] - public string NalLengthSize { get; set; } + public string? NalLengthSize { get; set; } /// /// Gets or sets the ltrt_cmixlev. /// /// The ltrt_cmixlev. [JsonPropertyName("ltrt_cmixlev")] - public string LtrtCmixlev { get; set; } + public string? LtrtCmixlev { get; set; } /// /// Gets or sets the ltrt_surmixlev. /// /// The ltrt_surmixlev. [JsonPropertyName("ltrt_surmixlev")] - public string LtrtSurmixlev { get; set; } + public string? LtrtSurmixlev { get; set; } /// /// Gets or sets the loro_cmixlev. /// /// The loro_cmixlev. [JsonPropertyName("loro_cmixlev")] - public string LoroCmixlev { get; set; } + public string? LoroCmixlev { get; set; } /// /// Gets or sets the loro_surmixlev. /// /// The loro_surmixlev. [JsonPropertyName("loro_surmixlev")] - public string LoroSurmixlev { get; set; } + public string? LoroSurmixlev { get; set; } /// /// Gets or sets the field_order. /// /// The field_order. [JsonPropertyName("field_order")] - public string FieldOrder { get; set; } + public string? FieldOrder { get; set; } /// /// Gets or sets the disposition. /// /// The disposition. [JsonPropertyName("disposition")] - public IReadOnlyDictionary Disposition { get; set; } + public IReadOnlyDictionary? Disposition { get; set; } /// /// Gets or sets the color range. /// /// The color range. [JsonPropertyName("color_range")] - public string ColorRange { get; set; } + public string? ColorRange { get; set; } /// /// Gets or sets the color space. /// /// The color space. [JsonPropertyName("color_space")] - public string ColorSpace { get; set; } + public string? ColorSpace { get; set; } /// /// Gets or sets the color transfer. /// /// The color transfer. [JsonPropertyName("color_transfer")] - public string ColorTransfer { get; set; } + public string? ColorTransfer { get; set; } /// /// Gets or sets the color primaries. /// /// The color primaries. [JsonPropertyName("color_primaries")] - public string ColorPrimaries { get; set; } + public string? ColorPrimaries { get; set; } /// /// Gets or sets the side_data_list. /// /// The side_data_list. [JsonPropertyName("side_data_list")] - public IReadOnlyList SideDataList { get; set; } + public IReadOnlyList? SideDataList { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 127bdd380d..d3e7b52315 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -697,24 +697,18 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaStream. private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo, IReadOnlyList frameInfoList) { - // These are mp4 chapters - if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase)) - { - // Edit: but these are also sometimes subtitles? - // return null; - } - var stream = new MediaStream { Codec = streamInfo.CodecName, Profile = streamInfo.Profile, + Width = streamInfo.Width, + Height = streamInfo.Height, Level = streamInfo.Level, Index = streamInfo.Index, PixelFormat = streamInfo.PixelFormat, NalLengthSize = streamInfo.NalLengthSize, TimeBase = streamInfo.TimeBase, - CodecTimeBase = streamInfo.CodecTimeBase, - IsAVC = streamInfo.IsAvc + CodecTimeBase = streamInfo.CodecTimeBase }; // Filter out junk @@ -774,10 +768,6 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedExternal = _localization.GetLocalizedString("External"); stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); - // Graphical subtitle may have width and height info - stream.Width = streamInfo.Width; - stream.Height = streamInfo.Height; - if (string.IsNullOrEmpty(stream.Title)) { // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler" @@ -790,6 +780,7 @@ namespace MediaBrowser.MediaEncoding.Probing } else if (streamInfo.CodecType == CodecType.Video) { + stream.IsAVC = streamInfo.IsAvc; stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate); stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); @@ -822,8 +813,6 @@ namespace MediaBrowser.MediaEncoding.Probing stream.Type = MediaStreamType.Video; } - stream.Width = streamInfo.Width; - stream.Height = streamInfo.Height; stream.AspectRatio = GetAspectRatio(streamInfo); if (streamInfo.BitsPerSample > 0) @@ -1091,8 +1080,8 @@ namespace MediaBrowser.MediaEncoding.Probing && width > 0 && height > 0)) { - width = info.Width; - height = info.Height; + width = info.Width.Value; + height = info.Height.Value; } if (width > 0 && height > 0) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index c443af32cf..11f81ff7d8 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -2,7 +2,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Frozen; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 8ebbd029ac..3369af0e84 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -209,8 +209,8 @@ namespace Jellyfin.MediaEncoding.Tests.Probing Assert.Equal("mkv,webm", res.Container); Assert.Equal(2, res.MediaStreams.Count); - - Assert.False(res.MediaStreams[0].IsAVC); + Assert.Equal(540, res.MediaStreams[0].Width); + Assert.Equal(360, res.MediaStreams[0].Height); } [Fact] -- cgit v1.2.3 From 42e8a780ca937b0f49b5e61b60adfda4abd465ec Mon Sep 17 00:00:00 2001 From: Molier Date: Mon, 30 Mar 2026 14:08:05 -0400 Subject: Backport pull request #16440 from jellyfin/release-10.11.z Remove -copyts and add -flush_packets 1 to subtitle extraction Original-merge: ec33c74ec44693a9ddb1e2f13bea90ef3c22267e Merged-by: Bond-009 Backported-by: Bond_009 --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.MediaEncoding') diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index aeaf7f4423..9aeac7221e 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -577,7 +577,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var outputPaths = new List(); var args = string.Format( CultureInfo.InvariantCulture, - "-i {0} -copyts", + "-i {0}", inputPath); foreach (var subtitleStream in subtitleStreams) @@ -602,7 +602,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles outputPaths.Add(outputPath); args += string.Format( CultureInfo.InvariantCulture, - " -map 0:{0} -an -vn -c:s {1} \"{2}\"", + " -map 0:{0} -an -vn -c:s {1} -flush_packets 1 \"{2}\"", streamIndex, outputCodec, outputPath); @@ -621,7 +621,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var outputPaths = new List(); var args = string.Format( CultureInfo.InvariantCulture, - "-i {0} -copyts", + "-i {0}", inputPath); foreach (var subtitleStream in subtitleStreams) @@ -647,7 +647,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles outputPaths.Add(outputPath); args += string.Format( CultureInfo.InvariantCulture, - " -map 0:{0} -an -vn -c:s {1} \"{2}\"", + " -map 0:{0} -an -vn -c:s {1} -flush_packets 1 \"{2}\"", streamIndex, outputCodec, outputPath); -- cgit v1.2.3