From fa99b1d81c5e8802e0aaecf4718c6c1413f2a1ac Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sun, 5 Oct 2025 12:53:17 +0200 Subject: [PATCH 1/7] fix: remote subtitles --- .../Subtitles/SubtitleEncoder.cs | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 49ac0fa033..095d65a542 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -172,24 +172,26 @@ namespace MediaBrowser.MediaEncoding.Subtitles private async Task 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 response = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetAsync(new Uri(fileInfo.Path), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); - using var reader = new StreamReader(stream, detected.Encoding); - var text = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); - return new MemoryStream(Encoding.UTF8.GetBytes(text)); - } + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).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)); } } @@ -941,42 +943,46 @@ namespace MediaBrowser.MediaEncoding.Subtitles .ConfigureAwait(false); } - var stream = await GetStream(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false); - await using (stream.ConfigureAwait(false)) + 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))) { - var result = await CharsetDetector.DetectFromStreamAsync(stream, 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; - } - - _logger.LogDebug("charset {0} detected for {Path}", charset, path); - - return charset; + charset = string.Empty; } + + _logger.LogDebug("charset {0} detected for {Path}", charset, path); + + return charset; } - private async Task GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) + private async Task 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 resp = await _httpClientFactory + .CreateClient(NamedClient.Default) + .GetAsync(new Uri(path), HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + resp.EnsureSuccessStatusCode(); + + using var s = await resp.Content.ReadAsStreamAsync(cancellationToken); + return await CharsetDetector.DetectFromStreamAsync(s, cancellationToken); + } 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"); } } From ddc613cd726604b8b881c4ea99485d7e7e3d6c3f Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sun, 5 Oct 2025 16:06:59 +0200 Subject: [PATCH 2/7] fix CA2007 --- .../Subtitles/SubtitleEncoder.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 095d65a542..63067cdb47 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -187,11 +187,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles response.EnsureSuccessStatusCode(); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - using var reader = new StreamReader(stream, detected.Encoding); - var text = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + var stream = await response.Content.ReadAsStreamAsync(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)); + } } } @@ -967,12 +970,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles { using var resp = await _httpClientFactory .CreateClient(NamedClient.Default) - .GetAsync(new Uri(path), HttpCompletionOption.ResponseHeadersRead, cancellationToken); + .GetAsync(new Uri(path), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); resp.EnsureSuccessStatusCode(); - using var s = await resp.Content.ReadAsStreamAsync(cancellationToken); - return await CharsetDetector.DetectFromStreamAsync(s, cancellationToken); + using var s = await resp.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return await CharsetDetector.DetectFromStreamAsync(s, cancellationToken).ConfigureAwait(false); } case MediaProtocol.File: From e5a2acd6dd901d4d9c57e80051c1b096efcf0f94 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sun, 5 Oct 2025 18:01:00 +0200 Subject: [PATCH 3/7] ise codec before path on format selection --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 63067cdb47..7fde71575b 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -223,7 +223,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 From 2168847a45721c567c69767be28c4ce51fe5523a Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sat, 29 Nov 2025 10:11:18 +0100 Subject: [PATCH 4/7] wip --- MediaBrowser.Model/Dlna/StreamInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 92404de508..9cbdf03e17 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1260,11 +1260,10 @@ 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 && 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 From 172b054f487c185efbe3f83639e1e896ca38dcb3 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Sat, 29 Nov 2025 10:20:18 +0100 Subject: [PATCH 5/7] wip --- MediaBrowser.Model/Dlna/StreamInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9cbdf03e17..3efb143bc3 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1264,6 +1264,7 @@ public class StreamInfo // Check conditions for potentially using the direct path if (stream.IsExternal // Must be external + && 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 From 8d8d38600ec542f32060a62f697fa944393edc48 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Mon, 1 Dec 2025 10:24:09 +0100 Subject: [PATCH 6/7] wip --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 7fde71575b..6b1c5f952f 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -181,13 +181,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles { _logger.LogDebug("charset {CharSet} detected for {Path}", detected.EncodingName, fileInfo.Path); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default) - .GetAsync(new Uri(fileInfo.Path), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + using var stream = await _httpClientFactory.CreateClient(NamedClient.Default) + .GetStreamAsync(new Uri(fileInfo.Path), cancellationToken) .ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using (stream.ConfigureAwait(false)) { using var reader = new StreamReader(stream, detected.Encoding); From 7f1a0ff6fce22d4e2f3919e92548d43cdc0229c9 Mon Sep 17 00:00:00 2001 From: lostb1t Date: Mon, 1 Dec 2025 12:00:08 +0100 Subject: [PATCH 7/7] wip --- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 6b1c5f952f..bf7ec05a96 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -965,15 +965,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { case MediaProtocol.Http: { - using var resp = await _httpClientFactory + using var stream = await _httpClientFactory .CreateClient(NamedClient.Default) - .GetAsync(new Uri(path), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .GetStreamAsync(new Uri(path), cancellationToken) .ConfigureAwait(false); - resp.EnsureSuccessStatusCode(); - - using var s = await resp.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await CharsetDetector.DetectFromStreamAsync(s, cancellationToken).ConfigureAwait(false); + return await CharsetDetector.DetectFromStreamAsync(stream, cancellationToken).ConfigureAwait(false); } case MediaProtocol.File: