From 4776d3b89cccf88717c245864a6d5dd5fcaa42df Mon Sep 17 00:00:00 2001 From: niksedk Date: Mon, 18 May 2026 21:35:02 +0200 Subject: [PATCH] SE UI batch convert: add "Try to use source encoding" option - fix #11019 Restores the WinForms-era "Try to use source encoding" entry to the target-encoding dropdown in batch convert (both the settings dialog and the main view). Also fixes a latent bug where the selected encoding never reached disk: SaveSubtitleFormat was passing the encoding string as the "title" arg to ToText() and then calling File.WriteAllTextAsync without an encoding, so output always landed as UTF-8 without BOM regardless of the user's choice. Encoding is now resolved through EncodingHelper.ResolveEncoding and passed to the write, with the new sentinel detecting per-file via LanguageAutoDetect. Defaults are pinned to UTF-8 with BOM (instead of "first item in the list") so existing installs don't migrate onto the new option. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../BatchConvertSettingsViewModel.cs | 9 +++-- .../BatchConvert/BatchConvertViewModel.cs | 4 +- .../Tools/BatchConvert/BatchConverter.cs | 5 ++- src/ui/Logic/EncodingHelper.cs | 38 +++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsViewModel.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsViewModel.cs index 2af5bffb2f2..d1913cf15fd 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsViewModel.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertSettingsViewModel.cs @@ -71,6 +71,7 @@ public partial class BatchConvertSettingsViewModel : ObservableObject public BatchConvertSettingsViewModel(IFolderHelper folderHelper, IWindowService windowService) { var encodings = EncodingHelper.GetEncodings().Select(p => p.DisplayName).ToList(); + encodings.Insert(0, EncodingHelper.TryToUseSourceEncoding); TargetEncodings = new ObservableCollection(encodings); OcrEngines = new ObservableCollection { "nOcr", "BinaryOcr", "Tesseract", "Ollama" }; @@ -159,8 +160,10 @@ private void LoadSettings() UseOutputFolder = !UseSourceFolder; OutputFolder = Se.Settings.Tools.BatchConvert.OutputFolder; Overwrite = Se.Settings.Tools.BatchConvert.Overwrite; - SelectedTargetEncoding = Se.Settings.Tools.BatchConvert.TargetEncoding; - SelectedOcrEngine = OcrEngines.FirstOrDefault(p => p == Se.Settings.Tools.BatchConvert.OcrEngine) ?? OcrEngines.First(); + SelectedTargetEncoding = TargetEncodings.FirstOrDefault(p => p == Se.Settings.Tools.BatchConvert.TargetEncoding) + ?? TargetEncodings.FirstOrDefault(p => p == TextEncoding.Utf8WithBom) + ?? TargetEncodings.First(); + SelectedOcrEngine = OcrEngines.FirstOrDefault(p => p == Se.Settings.Tools.BatchConvert.OcrEngine) ?? OcrEngines.First(); } private void SaveSettings() @@ -168,7 +171,7 @@ private void SaveSettings() Se.Settings.Tools.BatchConvert.SaveInSourceFolder = !UseOutputFolder; Se.Settings.Tools.BatchConvert.OutputFolder = OutputFolder; Se.Settings.Tools.BatchConvert.Overwrite = Overwrite; - Se.Settings.Tools.BatchConvert.TargetEncoding = SelectedTargetEncoding ?? TargetEncodings.First(); + Se.Settings.Tools.BatchConvert.TargetEncoding = SelectedTargetEncoding ?? TextEncoding.Utf8WithBom; Se.Settings.Tools.BatchConvert.LanguagePostFix = SelectedLanguagePostFix ?? Se.Language.General.TwoLetterLanguageCode; Se.Settings.Tools.BatchConvert.OcrEngine = SelectedOcrEngine ?? "nOcr"; diff --git a/src/ui/Features/Tools/BatchConvert/BatchConvertViewModel.cs b/src/ui/Features/Tools/BatchConvert/BatchConvertViewModel.cs index 6b384f4d307..64971b26014 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConvertViewModel.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConvertViewModel.cs @@ -339,6 +339,7 @@ public BatchConvertViewModel( _cancellationToken = _cancellationTokenSource.Token; _encodings = EncodingHelper.GetEncodings().Select(p => p.DisplayName).ToList(); + _encodings.Insert(0, EncodingHelper.TryToUseSourceEncoding); AutoTranslateModel = string.Empty; AutoTranslateUrl = string.Empty; @@ -773,7 +774,8 @@ private void UpdateOutputProperties() _encodings.FirstOrDefault(p => p == Se.Settings.Tools.BatchConvert.TargetEncoding); if (targetEncoding == null) { - targetEncoding = _encodings.First(); + targetEncoding = _encodings.FirstOrDefault(p => p == TextEncoding.Utf8WithBom) + ?? _encodings.First(); Se.Settings.Tools.BatchConvert.TargetEncoding = targetEncoding; } diff --git a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs index ea41b0e141e..97f312dcf72 100644 --- a/src/ui/Features/Tools/BatchConvert/BatchConverter.cs +++ b/src/ui/Features/Tools/BatchConvert/BatchConverter.cs @@ -1406,9 +1406,10 @@ private async Task SaveSubtitleFormat(BatchConvertItem item, SubtitleFormat targ } } - var converted = targetFormat.ToText(s, _config.TargetEncoding); + var converted = targetFormat.ToText(s, Path.GetFileNameWithoutExtension(item.FileName)); var path = MakeOutputFileName(item, targetFormat.Extension); - await File.WriteAllTextAsync(path, converted, cancellationToken); + var encoding = EncodingHelper.ResolveEncoding(_config.TargetEncoding, item.FileName); + await File.WriteAllTextAsync(path, converted, encoding, cancellationToken); item.Status = Se.Language.General.Converted; } catch (Exception exception) diff --git a/src/ui/Logic/EncodingHelper.cs b/src/ui/Logic/EncodingHelper.cs index 1e47055ca33..dd5db486f9a 100644 --- a/src/ui/Logic/EncodingHelper.cs +++ b/src/ui/Logic/EncodingHelper.cs @@ -1,11 +1,17 @@ using Nikse.SubtitleEdit.Core.Common; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; namespace Nikse.SubtitleEdit.Logic; public static class EncodingHelper { + // Stable sentinel for the batch-convert "use the source file's encoding" option. + // Stored verbatim in settings, so kept English (matches "UTF-8 with BOM" et al.). + public const string TryToUseSourceEncoding = "Try to use source encoding"; + public static List GetEncodings() { var encodingList = new List(); @@ -38,4 +44,36 @@ public static List GetRawEncodings() return encodingList; } + + /// + /// Resolves a target-encoding (as stored in + /// settings) to a concrete . Honors the + /// sentinel by detecting the source file's encoding. Falls back to UTF-8 with BOM + /// when the name is empty, unknown, or detection fails. + /// + public static Encoding ResolveEncoding(string? displayName, string? sourceFile) + { + if (string.Equals(displayName, TryToUseSourceEncoding, System.StringComparison.Ordinal)) + { + if (!string.IsNullOrEmpty(sourceFile) && File.Exists(sourceFile)) + { + return LanguageAutoDetect.GetEncodingFromFile(sourceFile); + } + return new UTF8Encoding(true); + } + + if (string.IsNullOrEmpty(displayName) || + string.Equals(displayName, TextEncoding.Utf8WithBom, System.StringComparison.Ordinal)) + { + return new UTF8Encoding(true); + } + + if (string.Equals(displayName, TextEncoding.Utf8WithoutBom, System.StringComparison.Ordinal)) + { + return new UTF8Encoding(false); + } + + var match = GetEncodings().FirstOrDefault(e => e.DisplayName == displayName); + return match?.Encoding ?? new UTF8Encoding(true); + } }