aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiels van Velzen <nielsvanvelzen@users.noreply.github.com>2026-01-26 12:00:14 +0100
committerGitHub <noreply@github.com>2026-01-26 12:00:14 +0100
commit360b6bcce1fc3f353ec23fa82ca415c3035b5710 (patch)
tree23851a7e6fdc8436e07436fff2c3d45d5360cdd6
parent92f3c8cf15b265020bca9386f434a8b968a338af (diff)
parent7f1a0ff6fce22d4e2f3919e92548d43cdc0229c9 (diff)
Merge pull request #14809 from lostb1t/fix/subtitleencoderHEADmaster
fix: prevent premature disposal of HTTP subtitle streams
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs78
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs4
2 files changed, 43 insertions, 39 deletions
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 49ac0fa03..bf7ec05a9 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -172,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));
}
}
}
@@ -218,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
@@ -941,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.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 9edb4115c..551bee89e 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -1252,11 +1252,11 @@ public class StreamInfo
stream.Index.ToString(CultureInfo.InvariantCulture),
startPositionTicks.ToString(CultureInfo.InvariantCulture),
subtitleProfile.Format);
- info.IsExternalUrl = false; // Default to API URL
+ info.IsExternalUrl = false;
// Check conditions for potentially using the direct path
if (stream.IsExternal // Must be external
- && MediaSource?.Protocol != MediaProtocol.File // Main media must not be a local file
+ && stream.SupportsExternalStream
&& string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) // Format must match (no conversion needed)
&& !string.IsNullOrEmpty(stream.Path) // Path must exist
&& Uri.TryCreate(stream.Path, UriKind.Absolute, out Uri? uriResult) // Path must be an absolute URI