aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs59
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs3
-rw-r--r--MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs82
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs60
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs60
7 files changed, 171 insertions, 99 deletions
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;
}
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(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
private static partial Regex CodecRegex();
- [GeneratedRegex("^\\s\\S{3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
+ [GeneratedRegex("^\\s\\S{2,3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
private static partial Regex FilterRegex();
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 73c5b88c8b..770965cab3 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1331,8 +1331,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public bool CanExtractSubtitles(string codec)
{
- // TODO is there ever a case when a subtitle can't be extracted??
- return true;
+ return _configurationManager.GetEncodingOptions().EnableSubtitleExtraction;
}
private sealed class ProcessWrapper : IDisposable
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
/// </summary>
/// <param name="dict">The dict.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private static Dictionary<string, string> ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary<string, string> dict)
+ private static Dictionary<string, string?> ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary<string, string?> dict)
{
- return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase);
+ return new Dictionary<string, string?>(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
/// </summary>
/// <value>The profile.</value>
[JsonPropertyName("profile")]
- public string Profile { get; set; }
+ public string? Profile { get; set; }
/// <summary>
/// Gets or sets the codec_name.
/// </summary>
/// <value>The codec_name.</value>
[JsonPropertyName("codec_name")]
- public string CodecName { get; set; }
+ public string? CodecName { get; set; }
/// <summary>
/// Gets or sets the codec_long_name.
/// </summary>
/// <value>The codec_long_name.</value>
[JsonPropertyName("codec_long_name")]
- public string CodecLongName { get; set; }
+ public string? CodecLongName { get; set; }
/// <summary>
/// Gets or sets the codec_type.
@@ -50,49 +48,49 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The sample_rate.</value>
[JsonPropertyName("sample_rate")]
- public string SampleRate { get; set; }
+ public string? SampleRate { get; set; }
/// <summary>
/// Gets or sets the channels.
/// </summary>
/// <value>The channels.</value>
[JsonPropertyName("channels")]
- public int Channels { get; set; }
+ public int? Channels { get; set; }
/// <summary>
/// Gets or sets the channel_layout.
/// </summary>
/// <value>The channel_layout.</value>
[JsonPropertyName("channel_layout")]
- public string ChannelLayout { get; set; }
+ public string? ChannelLayout { get; set; }
/// <summary>
/// Gets or sets the avg_frame_rate.
/// </summary>
/// <value>The avg_frame_rate.</value>
[JsonPropertyName("avg_frame_rate")]
- public string AverageFrameRate { get; set; }
+ public string? AverageFrameRate { get; set; }
/// <summary>
/// Gets or sets the duration.
/// </summary>
/// <value>The duration.</value>
[JsonPropertyName("duration")]
- public string Duration { get; set; }
+ public string? Duration { get; set; }
/// <summary>
/// Gets or sets the bit_rate.
/// </summary>
/// <value>The bit_rate.</value>
[JsonPropertyName("bit_rate")]
- public string BitRate { get; set; }
+ public string? BitRate { get; set; }
/// <summary>
/// Gets or sets the width.
/// </summary>
/// <value>The width.</value>
[JsonPropertyName("width")]
- public int Width { get; set; }
+ public int? Width { get; set; }
/// <summary>
/// Gets or sets the refs.
@@ -106,21 +104,21 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The height.</value>
[JsonPropertyName("height")]
- public int Height { get; set; }
+ public int? Height { get; set; }
/// <summary>
/// Gets or sets the display_aspect_ratio.
/// </summary>
/// <value>The display_aspect_ratio.</value>
[JsonPropertyName("display_aspect_ratio")]
- public string DisplayAspectRatio { get; set; }
+ public string? DisplayAspectRatio { get; set; }
/// <summary>
/// Gets or sets the tags.
/// </summary>
/// <value>The tags.</value>
[JsonPropertyName("tags")]
- public IReadOnlyDictionary<string, string> Tags { get; set; }
+ public IReadOnlyDictionary<string, string?>? Tags { get; set; }
/// <summary>
/// Gets or sets the bits_per_sample.
@@ -141,7 +139,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The r_frame_rate.</value>
[JsonPropertyName("r_frame_rate")]
- public string RFrameRate { get; set; }
+ public string? RFrameRate { get; set; }
/// <summary>
/// Gets or sets the has_b_frames.
@@ -155,70 +153,70 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The sample_aspect_ratio.</value>
[JsonPropertyName("sample_aspect_ratio")]
- public string SampleAspectRatio { get; set; }
+ public string? SampleAspectRatio { get; set; }
/// <summary>
/// Gets or sets the pix_fmt.
/// </summary>
/// <value>The pix_fmt.</value>
[JsonPropertyName("pix_fmt")]
- public string PixelFormat { get; set; }
+ public string? PixelFormat { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
/// <value>The level.</value>
[JsonPropertyName("level")]
- public int Level { get; set; }
+ public int? Level { get; set; }
/// <summary>
/// Gets or sets the time_base.
/// </summary>
/// <value>The time_base.</value>
[JsonPropertyName("time_base")]
- public string TimeBase { get; set; }
+ public string? TimeBase { get; set; }
/// <summary>
/// Gets or sets the start_time.
/// </summary>
/// <value>The start_time.</value>
[JsonPropertyName("start_time")]
- public string StartTime { get; set; }
+ public string? StartTime { get; set; }
/// <summary>
/// Gets or sets the codec_time_base.
/// </summary>
/// <value>The codec_time_base.</value>
[JsonPropertyName("codec_time_base")]
- public string CodecTimeBase { get; set; }
+ public string? CodecTimeBase { get; set; }
/// <summary>
/// Gets or sets the codec_tag.
/// </summary>
/// <value>The codec_tag.</value>
[JsonPropertyName("codec_tag")]
- public string CodecTag { get; set; }
+ public string? CodecTag { get; set; }
/// <summary>
- /// Gets or sets the codec_tag_string.
+ /// Gets or sets the codec_tag_string?.
/// </summary>
- /// <value>The codec_tag_string.</value>
- [JsonPropertyName("codec_tag_string")]
- public string CodecTagString { get; set; }
+ /// <value>The codec_tag_string?.</value>
+ [JsonPropertyName("codec_tag_string?")]
+ public string? CodecTagString { get; set; }
/// <summary>
/// Gets or sets the sample_fmt.
/// </summary>
/// <value>The sample_fmt.</value>
[JsonPropertyName("sample_fmt")]
- public string SampleFmt { get; set; }
+ public string? SampleFmt { get; set; }
/// <summary>
/// Gets or sets the dmix_mode.
/// </summary>
/// <value>The dmix_mode.</value>
[JsonPropertyName("dmix_mode")]
- public string DmixMode { get; set; }
+ public string? DmixMode { get; set; }
/// <summary>
/// Gets or sets the start_pts.
@@ -232,90 +230,90 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The is_avc.</value>
[JsonPropertyName("is_avc")]
- public bool IsAvc { get; set; }
+ public bool? IsAvc { get; set; }
/// <summary>
/// Gets or sets the nal_length_size.
/// </summary>
/// <value>The nal_length_size.</value>
[JsonPropertyName("nal_length_size")]
- public string NalLengthSize { get; set; }
+ public string? NalLengthSize { get; set; }
/// <summary>
/// Gets or sets the ltrt_cmixlev.
/// </summary>
/// <value>The ltrt_cmixlev.</value>
[JsonPropertyName("ltrt_cmixlev")]
- public string LtrtCmixlev { get; set; }
+ public string? LtrtCmixlev { get; set; }
/// <summary>
/// Gets or sets the ltrt_surmixlev.
/// </summary>
/// <value>The ltrt_surmixlev.</value>
[JsonPropertyName("ltrt_surmixlev")]
- public string LtrtSurmixlev { get; set; }
+ public string? LtrtSurmixlev { get; set; }
/// <summary>
/// Gets or sets the loro_cmixlev.
/// </summary>
/// <value>The loro_cmixlev.</value>
[JsonPropertyName("loro_cmixlev")]
- public string LoroCmixlev { get; set; }
+ public string? LoroCmixlev { get; set; }
/// <summary>
/// Gets or sets the loro_surmixlev.
/// </summary>
/// <value>The loro_surmixlev.</value>
[JsonPropertyName("loro_surmixlev")]
- public string LoroSurmixlev { get; set; }
+ public string? LoroSurmixlev { get; set; }
/// <summary>
/// Gets or sets the field_order.
/// </summary>
/// <value>The field_order.</value>
[JsonPropertyName("field_order")]
- public string FieldOrder { get; set; }
+ public string? FieldOrder { get; set; }
/// <summary>
/// Gets or sets the disposition.
/// </summary>
/// <value>The disposition.</value>
[JsonPropertyName("disposition")]
- public IReadOnlyDictionary<string, int> Disposition { get; set; }
+ public IReadOnlyDictionary<string, int>? Disposition { get; set; }
/// <summary>
/// Gets or sets the color range.
/// </summary>
/// <value>The color range.</value>
[JsonPropertyName("color_range")]
- public string ColorRange { get; set; }
+ public string? ColorRange { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
[JsonPropertyName("color_space")]
- public string ColorSpace { get; set; }
+ public string? ColorSpace { get; set; }
/// <summary>
/// Gets or sets the color transfer.
/// </summary>
/// <value>The color transfer.</value>
[JsonPropertyName("color_transfer")]
- public string ColorTransfer { get; set; }
+ public string? ColorTransfer { get; set; }
/// <summary>
/// Gets or sets the color primaries.
/// </summary>
/// <value>The color primaries.</value>
[JsonPropertyName("color_primaries")]
- public string ColorPrimaries { get; set; }
+ public string? ColorPrimaries { get; set; }
/// <summary>
/// Gets or sets the side_data_list.
/// </summary>
/// <value>The side_data_list.</value>
[JsonPropertyName("side_data_list")]
- public IReadOnlyList<MediaStreamInfoSideData> SideDataList { get; set; }
+ public IReadOnlyList<MediaStreamInfoSideData>? SideDataList { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 50f7716d86..3c6a03713f 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>
@@ -696,24 +697,18 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo, IReadOnlyList<MediaFrameInfo> 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
@@ -734,6 +729,9 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.Type = MediaStreamType.Audio;
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.LocalizedExternal = _localization.GetLocalizedString("External");
+ stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
+ ? null
+ : _localization.FindLanguageInfo(stream.Language)?.DisplayName;
stream.Channels = streamInfo.Channels;
@@ -772,10 +770,9 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.LocalizedForced = _localization.GetLocalizedString("Forced");
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;
+ stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
+ ? null
+ : _localization.FindLanguageInfo(stream.Language)?.DisplayName;
if (string.IsNullOrEmpty(stream.Title))
{
@@ -789,6 +786,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);
@@ -821,8 +819,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)
@@ -862,7 +858,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;
}
@@ -1090,8 +1086,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)
@@ -1154,6 +1150,34 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
+ /// 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).
+ /// </summary>
+ /// <param name="sar">The sample aspect ratio string in "N:D" format.</param>
+ /// <returns><c>true</c> if the SAR is within 1% of 1:1; otherwise <c>false</c>.</returns>
+ 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);
+ }
+
+ /// <summary>
/// Gets a frame rate from a string value in ffprobe output
/// This could be a number or in the format of 2997/125.
/// </summary>
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index bf7ec05a96..9aeac7221e 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;
@@ -564,7 +577,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var outputPaths = new List<string>();
var args = string.Format(
CultureInfo.InvariantCulture,
- "-i {0} -copyts",
+ "-i {0}",
inputPath);
foreach (var subtitleStream in subtitleStreams)
@@ -589,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);
@@ -608,7 +621,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var outputPaths = new List<string>();
var args = string.Format(
CultureInfo.InvariantCulture,
- "-i {0} -copyts",
+ "-i {0}",
inputPath);
foreach (var subtitleStream in subtitleStreams)
@@ -634,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);
@@ -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)