From 327bc071aa38777144ab4e50c9d8ebb3f6fdcb9d Mon Sep 17 00:00:00 2001 From: "Emrick, Joshua" Date: Tue, 9 Jun 2026 09:34:24 -0400 Subject: [PATCH 1/4] feat: add CLI options and support SBS/OU 3D subtitle modes - add named CLI arguments for mode, resolution, font size, and output path - support stereo modes: sbs (side-by-side) and ou (over-under) - write output .ass files next to the input file by default - add console logging, usage help, and argument validation - update ASS style generation for SBS and OU layouts - rename OU styles to Top and Bottom - fix HTML font color conversion to proper ASS color ordering - refactor runtime settings into an Options class Side Note: As a hearing impared person, I love this project and am glad it exists and I dont have to start from scratch. --- ConvertSRTto3DASS/Converter.cs | 479 +++++++++++++++++++++++++-------- 1 file changed, 369 insertions(+), 110 deletions(-) diff --git a/ConvertSRTto3DASS/Converter.cs b/ConvertSRTto3DASS/Converter.cs index f7a27d3..9d2e8cd 100644 --- a/ConvertSRTto3DASS/Converter.cs +++ b/ConvertSRTto3DASS/Converter.cs @@ -5,41 +5,227 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; -//SSA/ASS specification v4+ http://www.tcax.org/docs/ass-specs.htm -//SRT (SubRip) specification https://en.wikipedia.org/wiki/SubRip +// SSA/ASS specification v4+ http://www.tcax.org/docs/ass-specs.htm +// SRT (SubRip) specification https://en.wikipedia.org/wiki/SubRip namespace ConvertSRTto3DASS { class Converter { + private enum StereoMode + { + SBS, + OU + } + + private class Options + { + public string InputPath { get; set; } + public string OutputPath { get; set; } + public StereoMode Mode { get; set; } = StereoMode.SBS; + public int ResX { get; set; } = 384; + public int ResY { get; set; } = 288; + public int FontSize { get; set; } = 16; + } static void Main(string[] args) { + try + { + var options = ParseArguments(args); - var extracted = ExtractSubFromSRT(args[0]); - var style = CreateStandardStyle(); - var header = CreateHeader(args[0]); - var events = ProcessSubs(extracted); + if (options == null) + return; - var finished = "[Script Info]\n" + header + "\n\n[V4+ Styles]\n" + style + "\n\n[Events]\n" + events; - File.WriteAllText(Path.GetFileNameWithoutExtension(args[0]) + ".ass", finished, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); + if (!File.Exists(options.InputPath)) + { + Console.Error.WriteLine("Input file not found: " + options.InputPath); + return; + } + + if (string.IsNullOrWhiteSpace(options.OutputPath)) + { + options.OutputPath = Path.Combine( + Path.GetDirectoryName(options.InputPath) ?? "", + Path.GetFileNameWithoutExtension(options.InputPath) + ".ass"); + } + + Console.WriteLine("Input: " + options.InputPath); + Console.WriteLine("Output: " + options.OutputPath); + Console.WriteLine("Mode: " + options.Mode); + Console.WriteLine("Resolution:" + " " + options.ResX + "x" + options.ResY); + Console.WriteLine("Font size: " + options.FontSize); + + var extracted = ExtractSubFromSRT(options.InputPath); + Console.WriteLine("Subtitle blocks parsed: " + extracted.Count); + + var style = CreateStandardStyle(options.Mode, options.FontSize); + var header = CreateHeader(options.InputPath, options.ResY, options.ResX); + var eventsText = ProcessSubs(extracted, options.Mode); + + var finished = + "[Script Info]\n" + + header + + "\n\n[V4+ Styles]\n" + + style + + "\n\n[Events]\n" + + eventsText; + + File.WriteAllText(options.OutputPath, finished, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); + + Console.WriteLine("Conversion complete."); + } + catch (Exception ex) + { + Console.Error.WriteLine("ERROR: " + ex.Message); + Console.Error.WriteLine(ex.ToString()); + } + } + + private static Options ParseArguments(string[] args) + { + if (args == null || args.Length == 0) + { + PrintUsage(); + return null; + } + + var options = new Options(); + int i = 0; + + // First non-option argument is input file + if (args[0].StartsWith("--")) + { + Console.Error.WriteLine("First argument must be the input .srt file."); + PrintUsage(); + return null; + } + + options.InputPath = args[0]; + i = 1; + + while (i < args.Length) + { + string arg = args[i].Trim(); + + switch (arg.ToLowerInvariant()) + { + case "--mode": + EnsureValueExists(args, i, "--mode"); + options.Mode = ParseStereoMode(args[i + 1]); + i += 2; + break; + + case "--resx": + EnsureValueExists(args, i, "--resx"); + options.ResX = ParsePositiveInt(args[i + 1], "--resx"); + i += 2; + break; + + case "--resy": + EnsureValueExists(args, i, "--resy"); + options.ResY = ParsePositiveInt(args[i + 1], "--resy"); + i += 2; + break; + + case "--fontsize": + EnsureValueExists(args, i, "--fontsize"); + options.FontSize = ParsePositiveInt(args[i + 1], "--fontsize"); + i += 2; + break; + + case "--output": + EnsureValueExists(args, i, "--output"); + options.OutputPath = args[i + 1]; + i += 2; + break; + + case "--help": + case "-h": + case "/?": + PrintUsage(); + return null; + + default: + throw new ArgumentException("Unknown argument: " + arg); + } + } + + return options; + } + + private static void EnsureValueExists(string[] args, int index, string optionName) + { + if (index + 1 >= args.Length || args[index + 1].StartsWith("--")) + throw new ArgumentException("Missing value for " + optionName); + } + + private static int ParsePositiveInt(string value, string optionName) + { + if (!int.TryParse(value, out int parsed) || parsed <= 0) + throw new ArgumentException("Invalid value for " + optionName + ": " + value); + + return parsed; + } + + private static void PrintUsage() + { + Console.WriteLine("Usage:"); + Console.WriteLine(" ConvertSRTto3DASS.exe [options]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" --mode sbs|ou"); + Console.WriteLine(" --resx "); + Console.WriteLine(" --resy "); + Console.WriteLine(" --fontsize "); + Console.WriteLine(" --output "); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --resx 384 --resy 288"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs --fontsize 20"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --output ""movie_ou.ass"""); + } + + private static StereoMode ParseStereoMode(string mode) + { + if (string.IsNullOrWhiteSpace(mode)) + return StereoMode.SBS; + + switch (mode.Trim().ToLowerInvariant()) + { + case "sbs": + case "sidebyside": + case "side-by-side": + return StereoMode.SBS; + + case "ou": + case "overunder": + case "over-under": + case "tab": + case "topandbottom": + case "top-and-bottom": + return StereoMode.OU; + + default: + throw new ArgumentException( + "Invalid 3D type '" + mode + "'. Valid values are: sbs, ou"); + } } - //Is there a better way to do it? private static Dictionary regexReplacementDict = new Dictionary { - {new Regex(""), "{\\b1}"}, - {new Regex("()"),"{\\b0}"}, - {new Regex("()"),"{\\i1}"}, - {new Regex("()"),"{\\i0}"}, - {new Regex("()"),"{\\u1}" }, - {new Regex("()"),"{\\u0}"}, - {new Regex("()"),"{\\c&HFFFFFF&}"} //TODO custom colors. When you add custom colors, it needs to change this too - + { new Regex("", RegexOptions.IgnoreCase), "{\\b1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\b0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\i1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\i0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\u1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\u0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\c&HFFFFFF&}" } }; - private static Regex color = new Regex(""); - //TODO Add positional data to the formatting. + private static Regex color = new Regex("", RegexOptions.IgnoreCase); + private static string ChangeFormatting(string line) { foreach (var tuple in regexReplacementDict) @@ -50,68 +236,69 @@ namespace ConvertSRTto3DASS while (color.IsMatch(line)) { var match = color.Match(line); - string str_match = match.Value; - var color_str = str_match.Substring(14, 6); //RGB value in HEX + string rgb = match.Groups[1].Value; // RRGGBB - //ASS uses RGB value, but in reverse order so got to reverse it - var char_array = color_str.ToCharArray(); - Array.Reverse(char_array); - color_str = new string(char_array); + string rr = rgb.Substring(0, 2); + string gg = rgb.Substring(2, 2); + string bb = rgb.Substring(4, 2); - line = color.Replace(line, "{\\c&" + color_str + "&}"); + // ASS expects BBGGRR + string assColor = bb + gg + rr; - + line = color.Replace(line, "{\\c&H" + assColor + "&}", 1); } - return removeFormatting(line); + return RemoveFormatting(line); } + private static Regex reg = new Regex("<.+?>|(\\r)", RegexOptions.IgnoreCase); - //TODO: remove this and use the above method instead where it actually uses the formatting of the file - private static Regex reg = new Regex("<.+?>|(\\r)"); //Used to remove html tags used and line breaks - private static string removeFormatting(string line) + private static string RemoveFormatting(string line) { var replacement = reg.Replace(line, ""); return replacement; - } - - private static string ProcessSubs(List> srt) + private static string ProcessSubs(List> srt, StereoMode mode) { string endResult = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"; + + string style1 = mode == StereoMode.SBS ? "Right" : "Top"; + string style2 = mode == StereoMode.SBS ? "Left" : "Bottom"; + foreach (var events in srt) { var start = ConvertTimeStamp(events.Item1); var end = ConvertTimeStamp(events.Item2); - //First layer, for the right eye - var line = "Dialogue: " + 0 + "," + start + "," + end + ",Right,,0,0,0,," + events.Item3 + "\n"; - //Second layer, for the left eye - line += "Dialogue: " + 1 + "," + start + "," + end + ",Left,,0,0,0,," + events.Item3; + var line = "Dialogue: 0," + start + "," + end + "," + style1 + ",,0,0,0,," + events.Item3 + "\n"; + line += "Dialogue: 1," + start + "," + end + "," + style2 + ",,0,0,0,," + events.Item3; endResult += line + "\n"; } + return endResult; - } - private static string ConvertTimeStamp(string timeStamp) { - //Change double digit hour (for .srt) to single digit hour marker (for .ass) <- very crude var tmp = timeStamp.Substring(1); - - //Change thousands of a second hunderdth second - tmp = tmp.Substring(0, tmp.Length - 1);//TODO: Do actual rounding + tmp = tmp.Substring(0, tmp.Length - 1); return tmp.Replace(",", "."); } - //TODO: Make this human readable - //TODO: Add adjustable parameters - private static string CreateStandardStyle() + private static string CreateStandardStyle(StereoMode stereoMode, int fontSize) { - string style = "Format: " + + if (stereoMode == StereoMode.SBS) + return CreateSbsStyle(fontSize); + + return CreateOuStyle(fontSize); + } + + private static string CreateSbsStyle(int fontSize) + { + string style = + "Format: " + "Name, " + "Fontname, " + "Fontsize, " + @@ -139,7 +326,7 @@ namespace ConvertSRTto3DASS "Style: " + "Right," + "Arial," + - "16," + + fontSize + "," + "&Hffffff," + "&Hffffff," + "&H0," + @@ -161,11 +348,10 @@ namespace ConvertSRTto3DASS "10," + "0\n" + - "Style: " + "Left," + "Arial," + - "16," + + fontSize + "," + "&Hffffff," + "&Hffffff," + "&H0," + @@ -183,108 +369,181 @@ namespace ConvertSRTto3DASS "0," + "2," + "0," + - "192 ," + + "192," + "10," + "0"; + + return style; + } + + private static string CreateOuStyle(int fontSize) + { + string style = + "Format: " + + "Name, " + + "Fontname, " + + "Fontsize, " + + "PrimaryColour, " + + "SecondaryColour, " + + "OutlineColour, " + + "BackColour, " + + "Bold, " + + "Italic, " + + "Underline, " + + "StrikeOut, " + + "ScaleX, " + + "ScaleY, " + + "Spacing, " + + "Angle, " + + "BorderStyle, " + + "Outline, " + + "Shadow, " + + "Alignment, " + + "MarginL, " + + "MarginR, " + + "MarginV, " + + "Encoding\n" + + + "Style: " + + "Top," + + "Arial," + + fontSize + "," + + "&Hffffff," + + "&Hffffff," + + "&H0," + + "&H0," + + "0," + + "0," + + "0," + + "0," + + "100," + + "100," + + "0," + + "0," + + "1," + + "1," + + "0," + + "2," + + "0," + + "0," + + "154," + + "0\n" + + + "Style: " + + "Bottom," + + "Arial," + + fontSize + "," + + "&Hffffff," + + "&Hffffff," + + "&H0," + + "&H0," + + "0," + + "0," + + "0," + + "0," + + "100," + + "100," + + "0," + + "0," + + "1," + + "1," + + "0," + + "2," + + "0," + + "0," + + "10," + + "0"; + return style; } - //TODO: create a system where you can actually give paramaters to it private static string CreateHeader(string file, int resY = 288, int resX = 384) { var name = Path.GetFileNameWithoutExtension(file); - string scriptinfo = "; Generated by ConvertSRTto3D\n" + + string scriptinfo = + "; Generated by ConvertSRTto3D\n" + "Title: " + name + "\n" + "ScriptType: v4.00+\n" + "Collisions: Normal\n" + "PlayResX: " + resX + "\n" + "PlayResY: " + resY + "\n" + "ScaledBorderAndShadow: yes"; + return scriptinfo; } private static string ReadTextSmart(string path) { - // Try reading as UTF-8 with BOM detection using (var reader = new StreamReader(path, Encoding.UTF8, detectEncodingFromByteOrderMarks: true)) { string text = reader.ReadToEnd(); - // If UTF-8 decoding produced replacement character , fallback to CP1252 - if (text.Contains('\uFFFD')) + if (text.Contains('\uFFFD')) text = File.ReadAllText(path, Encoding.GetEncoding(1252)); return text; } } - //start, end, text, format <- tuple format - //Note - I want to change the tuple as format bit is useless as the data is carried in the text field for both .srt and .ass - //but I could reprepose it for any positional data private static List> ExtractSubFromSRT(string file) { - - var converted = new List>(); - - var timestamp_start = ""; - var timestamp_end = ""; - var subtitles = ""; + var results = new List>(); string srt = ReadTextSmart(file); - - int dialogNumber = 1; // Which dialog we are on - int dialogLine = 0; // Which line of the dialog block we are on - int linecounter = 0; //Where we are in the .srt, meant for debugging + srt = srt.Replace("\r\n", "\n").Replace("\r", "\n"); - foreach (string line in srt.Split('\n')) + var blocks = Regex.Split(srt.Trim(), @"\n\s*\n"); + + int expectedDialogNumber = 1; + + foreach (var block in blocks) { - linecounter++; - //Empty line assumes that the next line with event number thus previous dialog is finished and can be saved - if (line == "" | line == "\r") + var lines = block + .Split(new[] { '\n' }, StringSplitOptions.None) + .Select(l => l.Trim()) + .Where(l => l.Length > 0) + .ToList(); + + if (lines.Count < 3) + continue; + + if (!int.TryParse(lines[0], out int dialogNumber)) { - dialogLine = 0; - converted.Add(new Tuple(timestamp_start, timestamp_end, ChangeFormatting(subtitles), "")); - subtitles = ""; + Console.Error.WriteLine($"Skipping malformed block: expected subtitle number, got [{lines[0]}]"); continue; } - //Handle the first line - if (dialogLine == 0) + if (dialogNumber != expectedDialogNumber) { - //Check that the line is an actual number - if (int.TryParse(line, out int k) && k == dialogNumber) - { - dialogNumber++; - dialogLine++; - continue; - } + Console.Error.WriteLine( + $"Warning: expected subtitle number {expectedDialogNumber}, but found {dialogNumber}."); + expectedDialogNumber = dialogNumber; + } + expectedDialogNumber++; - Console.Error.WriteLine("Something went wrong"); - Console.Error.WriteLine("Something wrong on line:" + linecounter); - System.Environment.Exit(-1); + var timeMatch = Regex.Match( + lines[1], + @"^(?\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(?\d{2}:\d{2}:\d{2},\d{3})$"); + + if (!timeMatch.Success) + { + Console.Error.WriteLine($"Skipping malformed timestamp line: [{lines[1]}]"); + continue; } - //Timestamp extraction - if (dialogLine == 1) - { - timestamp_start = line.Substring(0, 12); - timestamp_end = line.Substring(17, 12); - dialogLine++; - } - else - { - if (subtitles == "") - { - subtitles = line; - subtitles = subtitles.Replace("\\.r", ""); - } - else - { - subtitles = subtitles + "\\N" + line; - } - } + string timestampStart = timeMatch.Groups["start"].Value; + string timestampEnd = timeMatch.Groups["end"].Value; + + string subtitleText = string.Join("\\N", lines.Skip(2)); + + results.Add(new Tuple( + timestampStart, + timestampEnd, + ChangeFormatting(subtitleText), + "")); } - return converted; + + return results; } } -} +} \ No newline at end of file From 470e23291f0924c3f263c1e7c661b4ee5f452903 Mon Sep 17 00:00:00 2001 From: "Emrick, Joshua" Date: Tue, 9 Jun 2026 11:16:53 -0400 Subject: [PATCH 2/4] feat: add red-green 3D mode and Windows Forms GUI - add rg (red-green) anaglyph subtitle mode with configurable horizontal offset - extend CLI mode handling to support sbs, ou, and rg - add offsetx option for anaglyph subtitle positioning - add Windows Forms GUI project for configuring and running conversions - add file pickers for input/output paths in the GUI - add GUI controls for mode, resolution, font size, and offset - add status output and mode-aware UI behavior in the GUI - update mode selection handling to support user-friendly display labels --- ConvertSRTto3DASS.sln | 12 +- ConvertSRTto3DASS/Converter.cs | 217 +++++++++--- ConvertSRTto3DASSGUI/App.config | 6 + .../ConvertSRTto3DASSGUI.csproj | 89 +++++ ConvertSRTto3DASSGUI/Form1.Designer.cs | 330 ++++++++++++++++++ ConvertSRTto3DASSGUI/Form1.cs | 158 +++++++++ ConvertSRTto3DASSGUI/Form1.resx | 120 +++++++ ConvertSRTto3DASSGUI/Program.cs | 22 ++ .../Properties/AssemblyInfo.cs | 33 ++ .../Properties/Resources.Designer.cs | 71 ++++ .../Properties/Resources.resx | 117 +++++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + 13 files changed, 1168 insertions(+), 44 deletions(-) create mode 100644 ConvertSRTto3DASSGUI/App.config create mode 100644 ConvertSRTto3DASSGUI/ConvertSRTto3DASSGUI.csproj create mode 100644 ConvertSRTto3DASSGUI/Form1.Designer.cs create mode 100644 ConvertSRTto3DASSGUI/Form1.cs create mode 100644 ConvertSRTto3DASSGUI/Form1.resx create mode 100644 ConvertSRTto3DASSGUI/Program.cs create mode 100644 ConvertSRTto3DASSGUI/Properties/AssemblyInfo.cs create mode 100644 ConvertSRTto3DASSGUI/Properties/Resources.Designer.cs create mode 100644 ConvertSRTto3DASSGUI/Properties/Resources.resx create mode 100644 ConvertSRTto3DASSGUI/Properties/Settings.Designer.cs create mode 100644 ConvertSRTto3DASSGUI/Properties/Settings.settings diff --git a/ConvertSRTto3DASS.sln b/ConvertSRTto3DASS.sln index 5060727..c0a0217 100644 --- a/ConvertSRTto3DASS.sln +++ b/ConvertSRTto3DASS.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30523.141 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35208.52 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConvertSRTto3DASS", "ConvertSRTto3DASS\ConvertSRTto3DASS.csproj", "{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertSRTto3DASS", "ConvertSRTto3DASS\ConvertSRTto3DASS.csproj", "{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConvertSRTto3DASSGUI", "ConvertSRTto3DASSGUI\ConvertSRTto3DASSGUI.csproj", "{8C44F434-2097-4FA3-BEE1-9569FB99F49C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Release|Any CPU.Build.0 = Release|Any CPU + {8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ConvertSRTto3DASS/Converter.cs b/ConvertSRTto3DASS/Converter.cs index 9d2e8cd..7f9bd58 100644 --- a/ConvertSRTto3DASS/Converter.cs +++ b/ConvertSRTto3DASS/Converter.cs @@ -10,12 +10,13 @@ using System.Text.RegularExpressions; namespace ConvertSRTto3DASS { - class Converter + public class Converter { private enum StereoMode { SBS, - OU + OU, + RG //Experimental } private class Options @@ -26,9 +27,10 @@ namespace ConvertSRTto3DASS public int ResX { get; set; } = 384; public int ResY { get; set; } = 288; public int FontSize { get; set; } = 16; + public int OffsetX { get; set; } = 4; // used for RG anaglyph } - static void Main(string[] args) + public static void Main(string[] args) { try { @@ -50,18 +52,19 @@ namespace ConvertSRTto3DASS Path.GetFileNameWithoutExtension(options.InputPath) + ".ass"); } - Console.WriteLine("Input: " + options.InputPath); - Console.WriteLine("Output: " + options.OutputPath); - Console.WriteLine("Mode: " + options.Mode); - Console.WriteLine("Resolution:" + " " + options.ResX + "x" + options.ResY); - Console.WriteLine("Font size: " + options.FontSize); + Console.WriteLine("Input: " + options.InputPath); + Console.WriteLine("Output: " + options.OutputPath); + Console.WriteLine("Mode: " + options.Mode); + Console.WriteLine("Resolution: " + options.ResX + "x" + options.ResY); + Console.WriteLine("Font size: " + options.FontSize); + Console.WriteLine("OffsetX: " + options.OffsetX); var extracted = ExtractSubFromSRT(options.InputPath); Console.WriteLine("Subtitle blocks parsed: " + extracted.Count); var style = CreateStandardStyle(options.Mode, options.FontSize); var header = CreateHeader(options.InputPath, options.ResY, options.ResX); - var eventsText = ProcessSubs(extracted, options.Mode); + var eventsText = ProcessSubs(extracted, options); var finished = "[Script Info]\n" + @@ -93,7 +96,6 @@ namespace ConvertSRTto3DASS var options = new Options(); int i = 0; - // First non-option argument is input file if (args[0].StartsWith("--")) { Console.Error.WriteLine("First argument must be the input .srt file."); @@ -140,6 +142,12 @@ namespace ConvertSRTto3DASS i += 2; break; + case "--offsetx": + EnsureValueExists(args, i, "--offsetx"); + options.OffsetX = ParseNonNegativeInt(args[i + 1], "--offsetx"); + i += 2; + break; + case "--help": case "-h": case "/?": @@ -168,23 +176,32 @@ namespace ConvertSRTto3DASS return parsed; } + private static int ParseNonNegativeInt(string value, string optionName) + { + if (!int.TryParse(value, out int parsed) || parsed < 0) + throw new ArgumentException("Invalid value for " + optionName + ": " + value); + + return parsed; + } + private static void PrintUsage() { Console.WriteLine("Usage:"); Console.WriteLine(" ConvertSRTto3DASS.exe [options]"); Console.WriteLine(); Console.WriteLine("Options:"); - Console.WriteLine(" --mode sbs|ou"); + Console.WriteLine(" --mode sbs|ou|rg"); Console.WriteLine(" --resx "); Console.WriteLine(" --resy "); Console.WriteLine(" --fontsize "); + Console.WriteLine(" --offsetx (used for rg mode)"); Console.WriteLine(" --output "); Console.WriteLine(); Console.WriteLine("Examples:"); Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs"); Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --resx 384 --resy 288"); - Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs --fontsize 20"); - Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --output ""movie_ou.ass"""); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode rg --offsetx 4"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode rg --fontsize 20 --output ""movie_rg.ass"""); } private static StereoMode ParseStereoMode(string mode) @@ -207,9 +224,16 @@ namespace ConvertSRTto3DASS case "top-and-bottom": return StereoMode.OU; + case "rg": + case "redgreen": + case "red-green": + case "anaglyph": + case "anaglyph-rg": + return StereoMode.RG; + default: throw new ArgumentException( - "Invalid 3D type '" + mode + "'. Valid values are: sbs, ou"); + "Invalid 3D type '" + mode + "'. Valid values are: sbs, ou, rg"); } } @@ -259,22 +283,42 @@ namespace ConvertSRTto3DASS return replacement; } - private static string ProcessSubs(List> srt, StereoMode mode) + private static string ProcessSubs(List> srt, Options options) { string endResult = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"; - string style1 = mode == StereoMode.SBS ? "Right" : "Top"; - string style2 = mode == StereoMode.SBS ? "Left" : "Bottom"; - foreach (var events in srt) { var start = ConvertTimeStamp(events.Item1); var end = ConvertTimeStamp(events.Item2); + var text = events.Item3; - var line = "Dialogue: 0," + start + "," + end + "," + style1 + ",,0,0,0,," + events.Item3 + "\n"; - line += "Dialogue: 1," + start + "," + end + "," + style2 + ",,0,0,0,," + events.Item3; + if (options.Mode == StereoMode.SBS) + { + var line = "Dialogue: 0," + start + "," + end + ",Right,,0,0,0,," + text + "\n"; + line += "Dialogue: 1," + start + "," + end + ",Left,,0,0,0,," + text; + endResult += line + "\n"; + } + else if (options.Mode == StereoMode.OU) + { + var line = "Dialogue: 0," + start + "," + end + ",Top,,0,0,0,," + text + "\n"; + line += "Dialogue: 1," + start + "," + end + ",Bottom,,0,0,0,," + text; + endResult += line + "\n"; + } + else if (options.Mode == StereoMode.RG) + { + int centerX = options.ResX / 2; + int y = options.ResY - 18; // near bottom + int redX = centerX - options.OffsetX; + int greenX = centerX + options.OffsetX; - endResult += line + "\n"; + string redText = "{\\an2\\pos(" + redX + "," + y + ")}" + text; + string greenText = "{\\an2\\pos(" + greenX + "," + y + ")}" + text; + + var line = "Dialogue: 0," + start + "," + end + ",RedEye,,0,0,0,," + redText + "\n"; + line += "Dialogue: 1," + start + "," + end + ",GreenEye,,0,0,0,," + greenText; + endResult += line + "\n"; + } } return endResult; @@ -289,10 +333,17 @@ namespace ConvertSRTto3DASS private static string CreateStandardStyle(StereoMode stereoMode, int fontSize) { - if (stereoMode == StereoMode.SBS) - return CreateSbsStyle(fontSize); - - return CreateOuStyle(fontSize); + switch (stereoMode) + { + case StereoMode.SBS: + return CreateSbsStyle(fontSize); + case StereoMode.OU: + return CreateOuStyle(fontSize); + case StereoMode.RG: + return CreateRgStyle(fontSize); + default: + throw new ArgumentOutOfRangeException(nameof(stereoMode)); + } } private static string CreateSbsStyle(int fontSize) @@ -327,10 +378,10 @@ namespace ConvertSRTto3DASS "Right," + "Arial," + fontSize + "," + - "&Hffffff," + - "&Hffffff," + - "&H0," + - "&H0," + + "&HFFFFFF," + + "&HFFFFFF," + + "&H000000," + + "&H000000," + "0," + "0," + "0," + @@ -352,10 +403,10 @@ namespace ConvertSRTto3DASS "Left," + "Arial," + fontSize + "," + - "&Hffffff," + - "&Hffffff," + - "&H0," + - "&H0," + + "&HFFFFFF," + + "&HFFFFFF," + + "&H000000," + + "&H000000," + "0," + "0," + "0," + @@ -408,10 +459,10 @@ namespace ConvertSRTto3DASS "Top," + "Arial," + fontSize + "," + - "&Hffffff," + - "&Hffffff," + - "&H0," + - "&H0," + + "&HFFFFFF," + + "&HFFFFFF," + + "&H000000," + + "&H000000," + "0," + "0," + "0," + @@ -433,10 +484,94 @@ namespace ConvertSRTto3DASS "Bottom," + "Arial," + fontSize + "," + - "&Hffffff," + - "&Hffffff," + - "&H0," + - "&H0," + + "&HFFFFFF," + + "&HFFFFFF," + + "&H000000," + + "&H000000," + + "0," + + "0," + + "0," + + "0," + + "100," + + "100," + + "0," + + "0," + + "1," + + "1," + + "0," + + "2," + + "0," + + "0," + + "10," + + "0"; + + return style; + } + + private static string CreateRgStyle(int fontSize) + { + string style = + "Format: " + + "Name, " + + "Fontname, " + + "Fontsize, " + + "PrimaryColour, " + + "SecondaryColour, " + + "OutlineColour, " + + "BackColour, " + + "Bold, " + + "Italic, " + + "Underline, " + + "StrikeOut, " + + "ScaleX, " + + "ScaleY, " + + "Spacing, " + + "Angle, " + + "BorderStyle, " + + "Outline, " + + "Shadow, " + + "Alignment, " + + "MarginL, " + + "MarginR, " + + "MarginV, " + + "Encoding\n" + + + // ASS color format is BBGGRR + // Red = &H0000FF + "Style: " + + "RedEye," + + "Arial," + + fontSize + "," + + "&H0000FF," + + "&H0000FF," + + "&H000000," + + "&H000000," + + "0," + + "0," + + "0," + + "0," + + "100," + + "100," + + "0," + + "0," + + "1," + + "1," + + "0," + + "2," + + "0," + + "0," + + "10," + + "0\n" + + + // Green = &H00FF00 + "Style: " + + "GreenEye," + + "Arial," + + fontSize + "," + + "&H00FF00," + + "&H00FF00," + + "&H000000," + + "&H000000," + "0," + "0," + "0," + diff --git a/ConvertSRTto3DASSGUI/App.config b/ConvertSRTto3DASSGUI/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/ConvertSRTto3DASSGUI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/ConvertSRTto3DASSGUI.csproj b/ConvertSRTto3DASSGUI/ConvertSRTto3DASSGUI.csproj new file mode 100644 index 0000000..1e2adfe --- /dev/null +++ b/ConvertSRTto3DASSGUI/ConvertSRTto3DASSGUI.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {8C44F434-2097-4FA3-BEE1-9569FB99F49C} + WinExe + ConvertSRTto3DASSGUI + ConvertSRTto3DASSGUI + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {7d7925de-adbd-4a26-b12b-8f12b68d3bfe} + ConvertSRTto3DASS + + + + \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/Form1.Designer.cs b/ConvertSRTto3DASSGUI/Form1.Designer.cs new file mode 100644 index 0000000..5af3a36 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Form1.Designer.cs @@ -0,0 +1,330 @@ +namespace ConvertSRTto3DASSGUI +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + private System.Windows.Forms.Label lblInputFile; + private System.Windows.Forms.TextBox txtInputFile; + private System.Windows.Forms.Button btnBrowseInput; + + private System.Windows.Forms.Label lblOutputFile; + private System.Windows.Forms.TextBox txtOutputFile; + private System.Windows.Forms.Button btnBrowseOutput; + + private System.Windows.Forms.Label lblMode; + private System.Windows.Forms.ComboBox cmbMode; + + private System.Windows.Forms.Label lblResX; + private System.Windows.Forms.NumericUpDown nudResX; + + private System.Windows.Forms.Label lblResY; + private System.Windows.Forms.NumericUpDown nudResY; + + private System.Windows.Forms.Label lblFontSize; + private System.Windows.Forms.NumericUpDown nudFontSize; + + private System.Windows.Forms.Label lblOffsetX; + private System.Windows.Forms.NumericUpDown nudOffsetX; + + private System.Windows.Forms.Button btnConvert; + private System.Windows.Forms.TextBox txtStatus; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lblInputFile = new System.Windows.Forms.Label(); + this.txtInputFile = new System.Windows.Forms.TextBox(); + this.btnBrowseInput = new System.Windows.Forms.Button(); + this.lblOutputFile = new System.Windows.Forms.Label(); + this.txtOutputFile = new System.Windows.Forms.TextBox(); + this.btnBrowseOutput = new System.Windows.Forms.Button(); + this.lblMode = new System.Windows.Forms.Label(); + this.cmbMode = new System.Windows.Forms.ComboBox(); + this.lblResX = new System.Windows.Forms.Label(); + this.nudResX = new System.Windows.Forms.NumericUpDown(); + this.lblResY = new System.Windows.Forms.Label(); + this.nudResY = new System.Windows.Forms.NumericUpDown(); + this.lblFontSize = new System.Windows.Forms.Label(); + this.nudFontSize = new System.Windows.Forms.NumericUpDown(); + this.lblOffsetX = new System.Windows.Forms.Label(); + this.nudOffsetX = new System.Windows.Forms.NumericUpDown(); + this.btnConvert = new System.Windows.Forms.Button(); + this.txtStatus = new System.Windows.Forms.TextBox(); + ((System.ComponentModel.ISupportInitialize)(this.nudResX)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudResY)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudFontSize)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudOffsetX)).BeginInit(); + this.SuspendLayout(); + // + // lblInputFile + // + this.lblInputFile.AutoSize = true; + this.lblInputFile.Location = new System.Drawing.Point(24, 28); + this.lblInputFile.Name = "lblInputFile"; + this.lblInputFile.Size = new System.Drawing.Size(94, 16); + this.lblInputFile.TabIndex = 0; + this.lblInputFile.Text = "Input SRT File:"; + // + // txtInputFile + // + this.txtInputFile.Location = new System.Drawing.Point(140, 25); + this.txtInputFile.Name = "txtInputFile"; + this.txtInputFile.Size = new System.Drawing.Size(780, 22); + this.txtInputFile.TabIndex = 1; + // + // btnBrowseInput + // + this.btnBrowseInput.Location = new System.Drawing.Point(940, 23); + this.btnBrowseInput.Name = "btnBrowseInput"; + this.btnBrowseInput.Size = new System.Drawing.Size(120, 28); + this.btnBrowseInput.TabIndex = 2; + this.btnBrowseInput.Text = "Browse..."; + this.btnBrowseInput.UseVisualStyleBackColor = true; + this.btnBrowseInput.Click += new System.EventHandler(this.btnBrowseInput_Click); + // + // lblOutputFile + // + this.lblOutputFile.AutoSize = true; + this.lblOutputFile.Location = new System.Drawing.Point(24, 72); + this.lblOutputFile.Name = "lblOutputFile"; + this.lblOutputFile.Size = new System.Drawing.Size(103, 16); + this.lblOutputFile.TabIndex = 3; + this.lblOutputFile.Text = "Output ASS File:"; + // + // txtOutputFile + // + this.txtOutputFile.Location = new System.Drawing.Point(140, 69); + this.txtOutputFile.Name = "txtOutputFile"; + this.txtOutputFile.Size = new System.Drawing.Size(780, 22); + this.txtOutputFile.TabIndex = 4; + // + // btnBrowseOutput + // + this.btnBrowseOutput.Location = new System.Drawing.Point(940, 67); + this.btnBrowseOutput.Name = "btnBrowseOutput"; + this.btnBrowseOutput.Size = new System.Drawing.Size(120, 28); + this.btnBrowseOutput.TabIndex = 5; + this.btnBrowseOutput.Text = "Browse..."; + this.btnBrowseOutput.UseVisualStyleBackColor = true; + this.btnBrowseOutput.Click += new System.EventHandler(this.btnBrowseOutput_Click); + // + // lblMode + // + this.lblMode.AutoSize = true; + this.lblMode.Location = new System.Drawing.Point(24, 125); + this.lblMode.Name = "lblMode"; + this.lblMode.Size = new System.Drawing.Size(65, 16); + this.lblMode.TabIndex = 6; + this.lblMode.Text = "3D Mode:"; + // + // cmbMode + // + this.cmbMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbMode.FormattingEnabled = true; + this.cmbMode.Items.AddRange(new object[] { + "sbs (Side-By-Side)", + "ou (Over-Under)", + "rg (Red-Green)"}); + this.cmbMode.Location = new System.Drawing.Point(140, 122); + this.cmbMode.Name = "cmbMode"; + this.cmbMode.Size = new System.Drawing.Size(180, 24); + this.cmbMode.TabIndex = 7; + this.cmbMode.SelectedIndexChanged += new System.EventHandler(this.cmbMode_SelectedIndexChanged); + // + // lblResX + // + this.lblResX.AutoSize = true; + this.lblResX.Location = new System.Drawing.Point(24, 178); + this.lblResX.Name = "lblResX"; + this.lblResX.Size = new System.Drawing.Size(46, 16); + this.lblResX.TabIndex = 8; + this.lblResX.Text = "Res X:"; + // + // nudResX + // + this.nudResX.Location = new System.Drawing.Point(140, 176); + this.nudResX.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.nudResX.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudResX.Name = "nudResX"; + this.nudResX.Size = new System.Drawing.Size(120, 22); + this.nudResX.TabIndex = 9; + this.nudResX.Value = new decimal(new int[] { + 384, + 0, + 0, + 0}); + // + // lblResY + // + this.lblResY.AutoSize = true; + this.lblResY.Location = new System.Drawing.Point(300, 178); + this.lblResY.Name = "lblResY"; + this.lblResY.Size = new System.Drawing.Size(47, 16); + this.lblResY.TabIndex = 10; + this.lblResY.Text = "Res Y:"; + // + // nudResY + // + this.nudResY.Location = new System.Drawing.Point(360, 176); + this.nudResY.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.nudResY.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudResY.Name = "nudResY"; + this.nudResY.Size = new System.Drawing.Size(120, 22); + this.nudResY.TabIndex = 11; + this.nudResY.Value = new decimal(new int[] { + 288, + 0, + 0, + 0}); + // + // lblFontSize + // + this.lblFontSize.AutoSize = true; + this.lblFontSize.Location = new System.Drawing.Point(24, 228); + this.lblFontSize.Name = "lblFontSize"; + this.lblFontSize.Size = new System.Drawing.Size(65, 16); + this.lblFontSize.TabIndex = 12; + this.lblFontSize.Text = "Font Size:"; + // + // nudFontSize + // + this.nudFontSize.Location = new System.Drawing.Point(140, 226); + this.nudFontSize.Maximum = new decimal(new int[] { + 200, + 0, + 0, + 0}); + this.nudFontSize.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudFontSize.Name = "nudFontSize"; + this.nudFontSize.Size = new System.Drawing.Size(120, 22); + this.nudFontSize.TabIndex = 13; + this.nudFontSize.Value = new decimal(new int[] { + 16, + 0, + 0, + 0}); + // + // lblOffsetX + // + this.lblOffsetX.AutoSize = true; + this.lblOffsetX.Location = new System.Drawing.Point(300, 228); + this.lblOffsetX.Name = "lblOffsetX"; + this.lblOffsetX.Size = new System.Drawing.Size(55, 16); + this.lblOffsetX.TabIndex = 14; + this.lblOffsetX.Text = "Offset X:"; + // + // nudOffsetX + // + this.nudOffsetX.Location = new System.Drawing.Point(360, 226); + this.nudOffsetX.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.nudOffsetX.Name = "nudOffsetX"; + this.nudOffsetX.Size = new System.Drawing.Size(120, 22); + this.nudOffsetX.TabIndex = 15; + this.nudOffsetX.Value = new decimal(new int[] { + 4, + 0, + 0, + 0}); + // + // btnConvert + // + this.btnConvert.Location = new System.Drawing.Point(27, 283); + this.btnConvert.Name = "btnConvert"; + this.btnConvert.Size = new System.Drawing.Size(145, 40); + this.btnConvert.TabIndex = 16; + this.btnConvert.Text = "Convert"; + this.btnConvert.UseVisualStyleBackColor = true; + this.btnConvert.Click += new System.EventHandler(this.btnConvert_Click); + // + // txtStatus + // + this.txtStatus.Location = new System.Drawing.Point(27, 349); + this.txtStatus.Multiline = true; + this.txtStatus.Name = "txtStatus"; + this.txtStatus.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtStatus.Size = new System.Drawing.Size(1033, 225); + this.txtStatus.TabIndex = 17; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1088, 609); + this.Controls.Add(this.txtStatus); + this.Controls.Add(this.btnConvert); + this.Controls.Add(this.nudOffsetX); + this.Controls.Add(this.lblOffsetX); + this.Controls.Add(this.nudFontSize); + this.Controls.Add(this.lblFontSize); + this.Controls.Add(this.nudResY); + this.Controls.Add(this.lblResY); + this.Controls.Add(this.nudResX); + this.Controls.Add(this.lblResX); + this.Controls.Add(this.cmbMode); + this.Controls.Add(this.lblMode); + this.Controls.Add(this.btnBrowseOutput); + this.Controls.Add(this.txtOutputFile); + this.Controls.Add(this.lblOutputFile); + this.Controls.Add(this.btnBrowseInput); + this.Controls.Add(this.txtInputFile); + this.Controls.Add(this.lblInputFile); + this.Name = "Form1"; + this.Text = "Convert SRT to 3D ASS"; + ((System.ComponentModel.ISupportInitialize)(this.nudResX)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudResY)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudFontSize)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudOffsetX)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + } +} \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/Form1.cs b/ConvertSRTto3DASSGUI/Form1.cs new file mode 100644 index 0000000..5550cf3 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Form1.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; +using System.Windows.Forms; +using ConvertSRTto3DASS; + +namespace ConvertSRTto3DASSGUI +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + + if (cmbMode.Items.Count > 0) + cmbMode.SelectedIndex = 0; + + UpdateModeUI(); + } + + private void btnBrowseInput_Click(object sender, EventArgs e) + { + using (var ofd = new OpenFileDialog()) + { + ofd.Filter = "SRT files (*.srt)|*.srt|All files (*.*)|*.*"; + ofd.Title = "Select input SRT file"; + + if (ofd.ShowDialog() == DialogResult.OK) + { + txtInputFile.Text = ofd.FileName; + + if (string.IsNullOrWhiteSpace(txtOutputFile.Text)) + { + txtOutputFile.Text = Path.ChangeExtension(ofd.FileName, ".ass"); + } + + AppendStatus("Selected input file: " + ofd.FileName); + } + } + } + + private void btnBrowseOutput_Click(object sender, EventArgs e) + { + using (var sfd = new SaveFileDialog()) + { + sfd.Filter = "ASS files (*.ass)|*.ass|All files (*.*)|*.*"; + sfd.Title = "Select output ASS file"; + sfd.DefaultExt = "ass"; + + if (!string.IsNullOrWhiteSpace(txtInputFile.Text)) + { + sfd.FileName = Path.GetFileNameWithoutExtension(txtInputFile.Text) + ".ass"; + sfd.InitialDirectory = Path.GetDirectoryName(txtInputFile.Text); + } + + if (sfd.ShowDialog() == DialogResult.OK) + { + txtOutputFile.Text = sfd.FileName; + AppendStatus("Selected output file: " + sfd.FileName); + } + } + } + + private void cmbMode_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateModeUI(); + } + + private string GetSelectedModeValue() + { + string selected = cmbMode.SelectedItem?.ToString() ?? "sbs"; + int spaceIndex = selected.IndexOf(' '); + + if (spaceIndex > 0) + return selected.Substring(0, spaceIndex).Trim().ToLowerInvariant(); + + return selected.Trim().ToLowerInvariant(); + } + + private void btnConvert_Click(object sender, EventArgs e) + { + try + { + txtStatus.Clear(); + + string inputPath = txtInputFile.Text.Trim(); + string outputPath = txtOutputFile.Text.Trim(); + string mode = cmbMode.SelectedItem?.ToString() ?? "sbs"; + + if (string.IsNullOrWhiteSpace(inputPath)) + { + MessageBox.Show("Please select an input SRT file.", "Missing Input", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + if (!File.Exists(inputPath)) + { + MessageBox.Show("The selected input file does not exist.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + if (string.IsNullOrWhiteSpace(outputPath)) + { + outputPath = Path.ChangeExtension(inputPath, ".ass"); + txtOutputFile.Text = outputPath; + } + + int resX = (int)nudResX.Value; + int resY = (int)nudResY.Value; + int fontSize = (int)nudFontSize.Value; + int offsetX = (int)nudOffsetX.Value; + + AppendStatus("Starting conversion..."); + AppendStatus("Input: " + inputPath); + AppendStatus("Output: " + outputPath); + AppendStatus("Mode: " + mode); + AppendStatus("Resolution: " + resX + "x" + resY); + AppendStatus("Font Size: " + fontSize); + AppendStatus("OffsetX: " + offsetX); + + ConvertSRTto3DASS.Converter.Main(new[] + { + txtInputFile.Text, + "--mode", GetSelectedModeValue(), + "--resx", nudResX.Value.ToString(), + "--resy", nudResY.Value.ToString(), + "--fontsize", nudFontSize.Value.ToString(), + "--offsetx", nudOffsetX.Value.ToString(), + "--output", txtOutputFile.Text + }); + + AppendStatus("Conversion complete."); + MessageBox.Show("Conversion complete.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + AppendStatus("ERROR: " + ex.Message); + MessageBox.Show(ex.Message, "Conversion Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void UpdateModeUI() + { + string mode = cmbMode.SelectedItem?.ToString() ?? "sbs"; + bool isRg = mode.Equals("rg", StringComparison.OrdinalIgnoreCase); + + nudOffsetX.Enabled = isRg; + lblOffsetX.Enabled = isRg; + } + + private void AppendStatus(string message) + { + if (txtStatus.TextLength > 0) + txtStatus.AppendText(Environment.NewLine); + + txtStatus.AppendText(message); + } + } +} \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/Form1.resx b/ConvertSRTto3DASSGUI/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/Program.cs b/ConvertSRTto3DASSGUI/Program.cs new file mode 100644 index 0000000..967f155 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ConvertSRTto3DASSGUI +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/ConvertSRTto3DASSGUI/Properties/AssemblyInfo.cs b/ConvertSRTto3DASSGUI/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e2909cc --- /dev/null +++ b/ConvertSRTto3DASSGUI/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ConvertSRTto3DASSGUI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Stanley Black & Decker inc.")] +[assembly: AssemblyProduct("ConvertSRTto3DASSGUI")] +[assembly: AssemblyCopyright("Copyright © Stanley Black & Decker inc. 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8c44f434-2097-4fa3-bee1-9569fb99f49c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ConvertSRTto3DASSGUI/Properties/Resources.Designer.cs b/ConvertSRTto3DASSGUI/Properties/Resources.Designer.cs new file mode 100644 index 0000000..6f1a5cd --- /dev/null +++ b/ConvertSRTto3DASSGUI/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ConvertSRTto3DASSGUI.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ConvertSRTto3DASSGUI.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/ConvertSRTto3DASSGUI/Properties/Resources.resx b/ConvertSRTto3DASSGUI/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/ConvertSRTto3DASSGUI/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ConvertSRTto3DASSGUI/Properties/Settings.Designer.cs b/ConvertSRTto3DASSGUI/Properties/Settings.Designer.cs new file mode 100644 index 0000000..80b9f94 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ConvertSRTto3DASSGUI.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/ConvertSRTto3DASSGUI/Properties/Settings.settings b/ConvertSRTto3DASSGUI/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/ConvertSRTto3DASSGUI/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + From 5e4e907a2c6a45423ec334e8b0d07c26c0e98f90 Mon Sep 17 00:00:00 2001 From: "Emrick, Joshua" Date: Mon, 15 Jun 2026 09:50:26 -0400 Subject: [PATCH 3/4] Add resolution-aware subtitle scaling and configurable layout defaults - replace fixed RG subtitle bottom positioning with resolution-scaled Y offset - add centralized ScaleX, ScaleY, and ScaleFont helpers - scale RG horizontal eye offset based on output width - scale font size, SBS margins, and OU margins based on output/base resolution - change default baseline behavior from legacy 384x288 to tunable base resolution - add BaseResX/BaseResY options with 1280x720 defaults - add CLI options for: - --baseresx - --baseresy - --bottomoffset - --sbssidemargin - --outopmargin - --verticalmargin - replace remaining layout magic numbers with configurable options: - BottomOffset - SbsSideMargin - OuTopMargin - VerticalMargin - FontSize - OffsetX - preserve original numeric defaults for layout settings when not specified (based off of my personal experiences and preferences. - update WinForms designer to expose new converter settings - update WinForms code-behind to pass new options to Converter.Main - prefill GUI controls with converter-aligned default values - improve mode-based UI enable/disable behavior for SBS, OU, and RG settings - prepare shared-defaults approach via ConverterDefaults for keeping GUI and converter defaults in sync --- ConvertSRTto3DASS/Converter.cs | 249 ++++++++++++++++++------- ConvertSRTto3DASSGUI/Form1.Designer.cs | 243 ++++++++++++++++++++++-- ConvertSRTto3DASSGUI/Form1.cs | 109 +++++++++-- 3 files changed, 510 insertions(+), 91 deletions(-) diff --git a/ConvertSRTto3DASS/Converter.cs b/ConvertSRTto3DASS/Converter.cs index 7f9bd58..07ed217 100644 --- a/ConvertSRTto3DASS/Converter.cs +++ b/ConvertSRTto3DASS/Converter.cs @@ -16,20 +16,67 @@ namespace ConvertSRTto3DASS { SBS, OU, - RG //Experimental + RG // Experimental + } + + public static class ConverterDefaults + { + /// + ///720 as default is arbitary,it's my favorite resolution for space saving and most mobile 3d viewing doesn't benefit from much higher (IE google cardboard or Meta Quest Headset). + /// + public const int ResX = 1280; + public const int ResY = 720; + + public const int BaseResX = 1280; + public const int BaseResY = 720; + + public const int FontSize = 16; + public const int OffsetX = 4; + public const int BottomOffset = 18; + public const int SbsSideMargin = 640; + public const int OuTopMargin = 385; + public const int VerticalMargin = 25; + + public const string DefaultMode = "sbs"; } private class Options { public string InputPath { get; set; } public string OutputPath { get; set; } + public StereoMode Mode { get; set; } = StereoMode.SBS; - public int ResX { get; set; } = 384; - public int ResY { get; set; } = 288; - public int FontSize { get; set; } = 16; - public int OffsetX { get; set; } = 4; // used for RG anaglyph + + public int ResX { get; set; } = ConverterDefaults.ResX; + public int ResY { get; set; } = ConverterDefaults.ResY; + public int BaseResX { get; set; } = ConverterDefaults.BaseResX; + public int BaseResY { get; set; } = ConverterDefaults.BaseResY; + public int FontSize { get; set; } = ConverterDefaults.FontSize; + public int OffsetX { get; set; } = ConverterDefaults.OffsetX; + public int BottomOffset { get; set; } = ConverterDefaults.BottomOffset; + public int SbsSideMargin { get; set; } = ConverterDefaults.SbsSideMargin; + public int OuTopMargin { get; set; } = ConverterDefaults.OuTopMargin; + public int VerticalMargin { get; set; } = ConverterDefaults.VerticalMargin; } + private static readonly Dictionary RegexReplacementDict = + new Dictionary + { + { new Regex("", RegexOptions.IgnoreCase), "{\\b1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\b0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\i1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\i0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\u1}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\u0}" }, + { new Regex("", RegexOptions.IgnoreCase), "{\\c&HFFFFFF&}" } + }; + + private static readonly Regex ColorRegex = + new Regex("", RegexOptions.IgnoreCase); + + private static readonly Regex RemoveFormattingRegex = + new Regex("<.+?>|(\\r)", RegexOptions.IgnoreCase); + public static void Main(string[] args) { try @@ -48,21 +95,26 @@ namespace ConvertSRTto3DASS if (string.IsNullOrWhiteSpace(options.OutputPath)) { options.OutputPath = Path.Combine( - Path.GetDirectoryName(options.InputPath) ?? "", + Path.GetDirectoryName(options.InputPath) ?? string.Empty, Path.GetFileNameWithoutExtension(options.InputPath) + ".ass"); } - Console.WriteLine("Input: " + options.InputPath); - Console.WriteLine("Output: " + options.OutputPath); - Console.WriteLine("Mode: " + options.Mode); - Console.WriteLine("Resolution: " + options.ResX + "x" + options.ResY); - Console.WriteLine("Font size: " + options.FontSize); - Console.WriteLine("OffsetX: " + options.OffsetX); + Console.WriteLine("Input: " + options.InputPath); + Console.WriteLine("Output: " + options.OutputPath); + Console.WriteLine("Mode: " + options.Mode); + Console.WriteLine("Resolution: " + options.ResX + "x" + options.ResY); + Console.WriteLine("Base Res: " + options.BaseResX + "x" + options.BaseResY); + Console.WriteLine("Font size: " + options.FontSize); + Console.WriteLine("OffsetX: " + options.OffsetX); + Console.WriteLine("BottomOffset: " + options.BottomOffset); + Console.WriteLine("SbsSideMargin: " + options.SbsSideMargin); + Console.WriteLine("OuTopMargin: " + options.OuTopMargin); + Console.WriteLine("VerticalMargin: " + options.VerticalMargin); var extracted = ExtractSubFromSRT(options.InputPath); Console.WriteLine("Subtitle blocks parsed: " + extracted.Count); - var style = CreateStandardStyle(options.Mode, options.FontSize); + var style = CreateStandardStyle(options); var header = CreateHeader(options.InputPath, options.ResY, options.ResX); var eventsText = ProcessSubs(extracted, options); @@ -74,7 +126,10 @@ namespace ConvertSRTto3DASS "\n\n[Events]\n" + eventsText; - File.WriteAllText(options.OutputPath, finished, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); + File.WriteAllText( + options.OutputPath, + finished, + new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); Console.WriteLine("Conversion complete."); } @@ -130,24 +185,60 @@ namespace ConvertSRTto3DASS i += 2; break; + case "--baseresx": + EnsureValueExists(args, i, "--baseresx"); + options.BaseResX = ParsePositiveInt(args[i + 1], "--baseresx"); + i += 2; + break; + + case "--baseresy": + EnsureValueExists(args, i, "--baseresy"); + options.BaseResY = ParsePositiveInt(args[i + 1], "--baseresy"); + i += 2; + break; + case "--fontsize": EnsureValueExists(args, i, "--fontsize"); options.FontSize = ParsePositiveInt(args[i + 1], "--fontsize"); i += 2; break; - case "--output": - EnsureValueExists(args, i, "--output"); - options.OutputPath = args[i + 1]; - i += 2; - break; - case "--offsetx": EnsureValueExists(args, i, "--offsetx"); options.OffsetX = ParseNonNegativeInt(args[i + 1], "--offsetx"); i += 2; break; + case "--bottomoffset": + EnsureValueExists(args, i, "--bottomoffset"); + options.BottomOffset = ParseNonNegativeInt(args[i + 1], "--bottomoffset"); + i += 2; + break; + + case "--sbssidemargin": + EnsureValueExists(args, i, "--sbssidemargin"); + options.SbsSideMargin = ParseNonNegativeInt(args[i + 1], "--sbssidemargin"); + i += 2; + break; + + case "--outopmargin": + EnsureValueExists(args, i, "--outopmargin"); + options.OuTopMargin = ParseNonNegativeInt(args[i + 1], "--outopmargin"); + i += 2; + break; + + case "--verticalmargin": + EnsureValueExists(args, i, "--verticalmargin"); + options.VerticalMargin = ParseNonNegativeInt(args[i + 1], "--verticalmargin"); + i += 2; + break; + + case "--output": + EnsureValueExists(args, i, "--output"); + options.OutputPath = args[i + 1]; + i += 2; + break; + case "--help": case "-h": case "/?": @@ -193,15 +284,23 @@ namespace ConvertSRTto3DASS Console.WriteLine(" --mode sbs|ou|rg"); Console.WriteLine(" --resx "); Console.WriteLine(" --resy "); + Console.WriteLine(" --baseresx (scaling reference width, default 1280)"); + Console.WriteLine(" --baseresy (scaling reference height, default 720)"); Console.WriteLine(" --fontsize "); - Console.WriteLine(" --offsetx (used for rg mode)"); + Console.WriteLine(" --offsetx (RG eye separation)"); + Console.WriteLine(" --bottomoffset (RG bottom offset)"); + Console.WriteLine(" --sbssidemargin (SBS side margin)"); + Console.WriteLine(" --outopmargin (OU top subtitle margin)"); + Console.WriteLine(" --verticalmargin (general vertical/bottom margin)"); Console.WriteLine(" --output "); Console.WriteLine(); Console.WriteLine("Examples:"); Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs"); - Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --resx 384 --resy 288"); - Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode rg --offsetx 4"); - Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode rg --fontsize 20 --output ""movie_rg.ass"""); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode ou --resx 1280 --resy 720"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode rg --offsetx 6 --bottomoffset 24"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --resx 1920 --resy 1080 --baseresx 1280 --baseresy 720"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --sbssidemargin 220 --verticalmargin 16"); + Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --outopmargin 180 --fontsize 20 --output ""movie_custom.ass"""); } private static StereoMode ParseStereoMode(string mode) @@ -237,29 +336,31 @@ namespace ConvertSRTto3DASS } } - private static Dictionary regexReplacementDict = - new Dictionary { - { new Regex("", RegexOptions.IgnoreCase), "{\\b1}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\b0}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\i1}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\i0}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\u1}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\u0}" }, - { new Regex("", RegexOptions.IgnoreCase), "{\\c&HFFFFFF&}" } - }; + private static int ScaleX(int value, Options options) + { + return (int)Math.Round(value * (options.ResX / (double)options.BaseResX)); + } - private static Regex color = new Regex("", RegexOptions.IgnoreCase); + private static int ScaleY(int value, Options options) + { + return (int)Math.Round(value * (options.ResY / (double)options.BaseResY)); + } + + private static int ScaleFont(int fontSize, Options options) + { + return Math.Max(1, ScaleY(fontSize, options)); + } private static string ChangeFormatting(string line) { - foreach (var tuple in regexReplacementDict) + foreach (var tuple in RegexReplacementDict) { line = tuple.Key.Replace(line, tuple.Value); } - while (color.IsMatch(line)) + while (ColorRegex.IsMatch(line)) { - var match = color.Match(line); + var match = ColorRegex.Match(line); string rgb = match.Groups[1].Value; // RRGGBB string rr = rgb.Substring(0, 2); @@ -269,18 +370,15 @@ namespace ConvertSRTto3DASS // ASS expects BBGGRR string assColor = bb + gg + rr; - line = color.Replace(line, "{\\c&H" + assColor + "&}", 1); + line = ColorRegex.Replace(line, "{\\c&H" + assColor + "&}", 1); } return RemoveFormatting(line); } - private static Regex reg = new Regex("<.+?>|(\\r)", RegexOptions.IgnoreCase); - private static string RemoveFormatting(string line) { - var replacement = reg.Replace(line, ""); - return replacement; + return RemoveFormattingRegex.Replace(line, ""); } private static string ProcessSubs(List> srt, Options options) @@ -308,9 +406,12 @@ namespace ConvertSRTto3DASS else if (options.Mode == StereoMode.RG) { int centerX = options.ResX / 2; - int y = options.ResY - 18; // near bottom - int redX = centerX - options.OffsetX; - int greenX = centerX + options.OffsetX; + int scaledBottomOffset = ScaleY(options.BottomOffset, options); + int y = options.ResY - scaledBottomOffset; + + int scaledOffsetX = ScaleX(options.OffsetX, options); + int redX = centerX - scaledOffsetX; + int greenX = centerX + scaledOffsetX; string redText = "{\\an2\\pos(" + redX + "," + y + ")}" + text; string greenText = "{\\an2\\pos(" + greenX + "," + y + ")}" + text; @@ -331,23 +432,31 @@ namespace ConvertSRTto3DASS return tmp.Replace(",", "."); } - private static string CreateStandardStyle(StereoMode stereoMode, int fontSize) + private static string CreateStandardStyle(Options options) { - switch (stereoMode) + int scaledFontSize = ScaleFont(options.FontSize, options); + + switch (options.Mode) { case StereoMode.SBS: - return CreateSbsStyle(fontSize); + return CreateSbsStyle(scaledFontSize, options); + case StereoMode.OU: - return CreateOuStyle(fontSize); + return CreateOuStyle(scaledFontSize, options); + case StereoMode.RG: - return CreateRgStyle(fontSize); + return CreateRgStyle(scaledFontSize, options); + default: - throw new ArgumentOutOfRangeException(nameof(stereoMode)); + throw new ArgumentOutOfRangeException(nameof(options.Mode)); } } - private static string CreateSbsStyle(int fontSize) + private static string CreateSbsStyle(int fontSize, Options options) { + int sideMargin = ScaleX(options.SbsSideMargin, options); + int verticalMargin = ScaleY(options.VerticalMargin, options); + string style = "Format: " + "Name, " + @@ -394,9 +503,9 @@ namespace ConvertSRTto3DASS "1," + "0," + "2," + - "192," + + sideMargin + "," + "0," + - "10," + + verticalMargin + "," + "0\n" + "Style: " + @@ -420,15 +529,18 @@ namespace ConvertSRTto3DASS "0," + "2," + "0," + - "192," + - "10," + + sideMargin + "," + + verticalMargin + "," + "0"; return style; } - private static string CreateOuStyle(int fontSize) + private static string CreateOuStyle(int fontSize, Options options) { + int bottomMargin = ScaleY(options.VerticalMargin, options); + int topMargin = ScaleY(options.OuTopMargin, options); + string style = "Format: " + "Name, " + @@ -477,7 +589,7 @@ namespace ConvertSRTto3DASS "2," + "0," + "0," + - "154," + + topMargin + "," + "0\n" + "Style: " + @@ -502,14 +614,16 @@ namespace ConvertSRTto3DASS "2," + "0," + "0," + - "10," + + bottomMargin + "," + "0"; return style; } - private static string CreateRgStyle(int fontSize) + private static string CreateRgStyle(int fontSize, Options options) { + int verticalMargin = ScaleY(options.VerticalMargin, options); + string style = "Format: " + "Name, " + @@ -560,7 +674,7 @@ namespace ConvertSRTto3DASS "2," + "0," + "0," + - "10," + + verticalMargin + "," + "0\n" + // Green = &H00FF00 @@ -586,16 +700,17 @@ namespace ConvertSRTto3DASS "2," + "0," + "0," + - "10," + + verticalMargin + "," + "0"; return style; } - private static string CreateHeader(string file, int resY = 288, int resX = 384) + private static string CreateHeader(string file, int resY = 720, int resX = 1280) { var name = Path.GetFileNameWithoutExtension(file); - string scriptinfo = + + string scriptInfo = "; Generated by ConvertSRTto3D\n" + "Title: " + name + "\n" + "ScriptType: v4.00+\n" + @@ -604,7 +719,7 @@ namespace ConvertSRTto3DASS "PlayResY: " + resY + "\n" + "ScaledBorderAndShadow: yes"; - return scriptinfo; + return scriptInfo; } private static string ReadTextSmart(string path) @@ -654,6 +769,7 @@ namespace ConvertSRTto3DASS $"Warning: expected subtitle number {expectedDialogNumber}, but found {dialogNumber}."); expectedDialogNumber = dialogNumber; } + expectedDialogNumber++; var timeMatch = Regex.Match( @@ -668,7 +784,6 @@ namespace ConvertSRTto3DASS string timestampStart = timeMatch.Groups["start"].Value; string timestampEnd = timeMatch.Groups["end"].Value; - string subtitleText = string.Join("\\N", lines.Skip(2)); results.Add(new Tuple( diff --git a/ConvertSRTto3DASSGUI/Form1.Designer.cs b/ConvertSRTto3DASSGUI/Form1.Designer.cs index 5af3a36..96dc207 100644 --- a/ConvertSRTto3DASSGUI/Form1.Designer.cs +++ b/ConvertSRTto3DASSGUI/Form1.Designer.cs @@ -24,12 +24,30 @@ private System.Windows.Forms.Label lblResY; private System.Windows.Forms.NumericUpDown nudResY; + private System.Windows.Forms.Label lblBaseResX; + private System.Windows.Forms.NumericUpDown nudBaseResX; + + private System.Windows.Forms.Label lblBaseResY; + private System.Windows.Forms.NumericUpDown nudBaseResY; + private System.Windows.Forms.Label lblFontSize; private System.Windows.Forms.NumericUpDown nudFontSize; private System.Windows.Forms.Label lblOffsetX; private System.Windows.Forms.NumericUpDown nudOffsetX; + private System.Windows.Forms.Label lblBottomOffset; + private System.Windows.Forms.NumericUpDown nudBottomOffset; + + private System.Windows.Forms.Label lblSbsSideMargin; + private System.Windows.Forms.NumericUpDown nudSbsSideMargin; + + private System.Windows.Forms.Label lblOuTopMargin; + private System.Windows.Forms.NumericUpDown nudOuTopMargin; + + private System.Windows.Forms.Label lblVerticalMargin; + private System.Windows.Forms.NumericUpDown nudVerticalMargin; + private System.Windows.Forms.Button btnConvert; private System.Windows.Forms.TextBox txtStatus; @@ -66,16 +84,34 @@ this.nudResX = new System.Windows.Forms.NumericUpDown(); this.lblResY = new System.Windows.Forms.Label(); this.nudResY = new System.Windows.Forms.NumericUpDown(); + this.lblBaseResX = new System.Windows.Forms.Label(); + this.nudBaseResX = new System.Windows.Forms.NumericUpDown(); + this.lblBaseResY = new System.Windows.Forms.Label(); + this.nudBaseResY = new System.Windows.Forms.NumericUpDown(); this.lblFontSize = new System.Windows.Forms.Label(); this.nudFontSize = new System.Windows.Forms.NumericUpDown(); this.lblOffsetX = new System.Windows.Forms.Label(); this.nudOffsetX = new System.Windows.Forms.NumericUpDown(); + this.lblBottomOffset = new System.Windows.Forms.Label(); + this.nudBottomOffset = new System.Windows.Forms.NumericUpDown(); + this.lblSbsSideMargin = new System.Windows.Forms.Label(); + this.nudSbsSideMargin = new System.Windows.Forms.NumericUpDown(); + this.lblOuTopMargin = new System.Windows.Forms.Label(); + this.nudOuTopMargin = new System.Windows.Forms.NumericUpDown(); + this.lblVerticalMargin = new System.Windows.Forms.Label(); + this.nudVerticalMargin = new System.Windows.Forms.NumericUpDown(); this.btnConvert = new System.Windows.Forms.Button(); this.txtStatus = new System.Windows.Forms.TextBox(); ((System.ComponentModel.ISupportInitialize)(this.nudResX)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudResY)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBaseResX)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBaseResY)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudFontSize)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetX)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBottomOffset)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudSbsSideMargin)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudOuTopMargin)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudVerticalMargin)).BeginInit(); this.SuspendLayout(); // // lblInputFile @@ -179,7 +215,7 @@ this.nudResX.Size = new System.Drawing.Size(120, 22); this.nudResX.TabIndex = 9; this.nudResX.Value = new decimal(new int[] { - 384, + 1280, 0, 0, 0}); @@ -210,7 +246,69 @@ this.nudResY.Size = new System.Drawing.Size(120, 22); this.nudResY.TabIndex = 11; this.nudResY.Value = new decimal(new int[] { - 288, + 720, + 0, + 0, + 0}); + // + // lblBaseResX + // + this.lblBaseResX.AutoSize = true; + this.lblBaseResX.Location = new System.Drawing.Point(520, 178); + this.lblBaseResX.Name = "lblBaseResX"; + this.lblBaseResX.Size = new System.Drawing.Size(81, 16); + this.lblBaseResX.TabIndex = 12; + this.lblBaseResX.Text = "Base Res X:"; + // + // nudBaseResX + // + this.nudBaseResX.Location = new System.Drawing.Point(620, 176); + this.nudBaseResX.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.nudBaseResX.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudBaseResX.Name = "nudBaseResX"; + this.nudBaseResX.Size = new System.Drawing.Size(120, 22); + this.nudBaseResX.TabIndex = 13; + this.nudBaseResX.Value = new decimal(new int[] { + 1280, + 0, + 0, + 0}); + // + // lblBaseResY + // + this.lblBaseResY.AutoSize = true; + this.lblBaseResY.Location = new System.Drawing.Point(760, 178); + this.lblBaseResY.Name = "lblBaseResY"; + this.lblBaseResY.Size = new System.Drawing.Size(82, 16); + this.lblBaseResY.TabIndex = 14; + this.lblBaseResY.Text = "Base Res Y:"; + // + // nudBaseResY + // + this.nudBaseResY.Location = new System.Drawing.Point(860, 176); + this.nudBaseResY.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.nudBaseResY.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.nudBaseResY.Name = "nudBaseResY"; + this.nudBaseResY.Size = new System.Drawing.Size(120, 22); + this.nudBaseResY.TabIndex = 15; + this.nudBaseResY.Value = new decimal(new int[] { + 720, 0, 0, 0}); @@ -221,7 +319,7 @@ this.lblFontSize.Location = new System.Drawing.Point(24, 228); this.lblFontSize.Name = "lblFontSize"; this.lblFontSize.Size = new System.Drawing.Size(65, 16); - this.lblFontSize.TabIndex = 12; + this.lblFontSize.TabIndex = 16; this.lblFontSize.Text = "Font Size:"; // // nudFontSize @@ -239,7 +337,7 @@ 0}); this.nudFontSize.Name = "nudFontSize"; this.nudFontSize.Size = new System.Drawing.Size(120, 22); - this.nudFontSize.TabIndex = 13; + this.nudFontSize.TabIndex = 17; this.nudFontSize.Value = new decimal(new int[] { 16, 0, @@ -252,7 +350,7 @@ this.lblOffsetX.Location = new System.Drawing.Point(300, 228); this.lblOffsetX.Name = "lblOffsetX"; this.lblOffsetX.Size = new System.Drawing.Size(55, 16); - this.lblOffsetX.TabIndex = 14; + this.lblOffsetX.TabIndex = 18; this.lblOffsetX.Text = "Offset X:"; // // nudOffsetX @@ -265,43 +363,159 @@ 0}); this.nudOffsetX.Name = "nudOffsetX"; this.nudOffsetX.Size = new System.Drawing.Size(120, 22); - this.nudOffsetX.TabIndex = 15; + this.nudOffsetX.TabIndex = 19; this.nudOffsetX.Value = new decimal(new int[] { 4, 0, 0, 0}); // + // lblBottomOffset + // + this.lblBottomOffset.AutoSize = true; + this.lblBottomOffset.Location = new System.Drawing.Point(520, 228); + this.lblBottomOffset.Name = "lblBottomOffset"; + this.lblBottomOffset.Size = new System.Drawing.Size(89, 16); + this.lblBottomOffset.TabIndex = 20; + this.lblBottomOffset.Text = "Bottom Offset:"; + // + // nudBottomOffset + // + this.nudBottomOffset.Location = new System.Drawing.Point(620, 226); + this.nudBottomOffset.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.nudBottomOffset.Name = "nudBottomOffset"; + this.nudBottomOffset.Size = new System.Drawing.Size(120, 22); + this.nudBottomOffset.TabIndex = 21; + this.nudBottomOffset.Value = new decimal(new int[] { + 18, + 0, + 0, + 0}); + // + // lblSbsSideMargin + // + this.lblSbsSideMargin.AutoSize = true; + this.lblSbsSideMargin.Location = new System.Drawing.Point(24, 278); + this.lblSbsSideMargin.Name = "lblSbsSideMargin"; + this.lblSbsSideMargin.Size = new System.Drawing.Size(112, 16); + this.lblSbsSideMargin.TabIndex = 22; + this.lblSbsSideMargin.Text = "SBS Side Margin:"; + // + // nudSbsSideMargin + // + this.nudSbsSideMargin.Location = new System.Drawing.Point(140, 276); + this.nudSbsSideMargin.Maximum = new decimal(new int[] { + 5000, + 0, + 0, + 0}); + this.nudSbsSideMargin.Name = "nudSbsSideMargin"; + this.nudSbsSideMargin.Size = new System.Drawing.Size(120, 22); + this.nudSbsSideMargin.TabIndex = 23; + this.nudSbsSideMargin.Value = new decimal(new int[] { + 192, + 0, + 0, + 0}); + // + // lblOuTopMargin + // + this.lblOuTopMargin.AutoSize = true; + this.lblOuTopMargin.Location = new System.Drawing.Point(300, 278); + this.lblOuTopMargin.Name = "lblOuTopMargin"; + this.lblOuTopMargin.Size = new System.Drawing.Size(102, 16); + this.lblOuTopMargin.TabIndex = 24; + this.lblOuTopMargin.Text = "OU Top Margin:"; + // + // nudOuTopMargin + // + this.nudOuTopMargin.Location = new System.Drawing.Point(410, 276); + this.nudOuTopMargin.Maximum = new decimal(new int[] { + 5000, + 0, + 0, + 0}); + this.nudOuTopMargin.Name = "nudOuTopMargin"; + this.nudOuTopMargin.Size = new System.Drawing.Size(120, 22); + this.nudOuTopMargin.TabIndex = 25; + this.nudOuTopMargin.Value = new decimal(new int[] { + 154, + 0, + 0, + 0}); + // + // lblVerticalMargin + // + this.lblVerticalMargin.AutoSize = true; + this.lblVerticalMargin.Location = new System.Drawing.Point(560, 278); + this.lblVerticalMargin.Name = "lblVerticalMargin"; + this.lblVerticalMargin.Size = new System.Drawing.Size(99, 16); + this.lblVerticalMargin.TabIndex = 26; + this.lblVerticalMargin.Text = "Vertical Margin:"; + // + // nudVerticalMargin + // + this.nudVerticalMargin.Location = new System.Drawing.Point(670, 276); + this.nudVerticalMargin.Maximum = new decimal(new int[] { + 5000, + 0, + 0, + 0}); + this.nudVerticalMargin.Name = "nudVerticalMargin"; + this.nudVerticalMargin.Size = new System.Drawing.Size(120, 22); + this.nudVerticalMargin.TabIndex = 27; + this.nudVerticalMargin.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // // btnConvert // - this.btnConvert.Location = new System.Drawing.Point(27, 283); + this.btnConvert.Location = new System.Drawing.Point(27, 329); this.btnConvert.Name = "btnConvert"; this.btnConvert.Size = new System.Drawing.Size(145, 40); - this.btnConvert.TabIndex = 16; + this.btnConvert.TabIndex = 28; this.btnConvert.Text = "Convert"; this.btnConvert.UseVisualStyleBackColor = true; this.btnConvert.Click += new System.EventHandler(this.btnConvert_Click); // // txtStatus // - this.txtStatus.Location = new System.Drawing.Point(27, 349); + this.txtStatus.Location = new System.Drawing.Point(27, 392); this.txtStatus.Multiline = true; this.txtStatus.Name = "txtStatus"; this.txtStatus.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; this.txtStatus.Size = new System.Drawing.Size(1033, 225); - this.txtStatus.TabIndex = 17; + this.txtStatus.TabIndex = 29; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1088, 609); + this.ClientSize = new System.Drawing.Size(1088, 640); this.Controls.Add(this.txtStatus); this.Controls.Add(this.btnConvert); + this.Controls.Add(this.nudVerticalMargin); + this.Controls.Add(this.lblVerticalMargin); + this.Controls.Add(this.nudOuTopMargin); + this.Controls.Add(this.lblOuTopMargin); + this.Controls.Add(this.nudSbsSideMargin); + this.Controls.Add(this.lblSbsSideMargin); + this.Controls.Add(this.nudBottomOffset); + this.Controls.Add(this.lblBottomOffset); this.Controls.Add(this.nudOffsetX); this.Controls.Add(this.lblOffsetX); this.Controls.Add(this.nudFontSize); this.Controls.Add(this.lblFontSize); + this.Controls.Add(this.nudBaseResY); + this.Controls.Add(this.lblBaseResY); + this.Controls.Add(this.nudBaseResX); + this.Controls.Add(this.lblBaseResX); this.Controls.Add(this.nudResY); this.Controls.Add(this.lblResY); this.Controls.Add(this.nudResX); @@ -316,10 +530,17 @@ this.Controls.Add(this.lblInputFile); this.Name = "Form1"; this.Text = "Convert SRT to 3D ASS"; + this.Load += new System.EventHandler(this.Form1_Load); ((System.ComponentModel.ISupportInitialize)(this.nudResX)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudResY)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBaseResX)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBaseResY)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudFontSize)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetX)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudBottomOffset)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudSbsSideMargin)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudOuTopMargin)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudVerticalMargin)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); diff --git a/ConvertSRTto3DASSGUI/Form1.cs b/ConvertSRTto3DASSGUI/Form1.cs index 5550cf3..e909b0c 100644 --- a/ConvertSRTto3DASSGUI/Form1.cs +++ b/ConvertSRTto3DASSGUI/Form1.cs @@ -1,15 +1,29 @@ -using System; +using ConvertSRTto3DASS; +using System; using System.IO; using System.Windows.Forms; -using ConvertSRTto3DASS; +using static ConvertSRTto3DASS.Converter; namespace ConvertSRTto3DASSGUI { public partial class Form1 : Form { + // Keep these in sync with Converter.cs defaults + private const int DefaultResX = Converter.ConverterDefaults.ResX;//1280 + private const int DefaultResY = 720; + private const int DefaultBaseResX = 1280; + private const int DefaultBaseResY = 720; + private const int DefaultFontSize = 16; + private const int DefaultOffsetX = 4; + private const int DefaultBottomOffset = 18; + private const int DefaultSbsSideMargin = 192; + private const int DefaultOuTopMargin = 154; + private const int DefaultVerticalMargin = 10; + public Form1() { InitializeComponent(); + InitializeDefaults(); if (cmbMode.Items.Count > 0) cmbMode.SelectedIndex = 0; @@ -17,6 +31,39 @@ namespace ConvertSRTto3DASSGUI UpdateModeUI(); } + private void InitializeDefaults() + { + txtInputFile.Text = string.Empty; + txtOutputFile.Text = string.Empty; + + nudResX.Value = ConverterDefaults.ResX; + nudResY.Value = ConverterDefaults.ResY; + nudBaseResX.Value = ConverterDefaults.BaseResX; + nudBaseResY.Value = ConverterDefaults.BaseResY; + nudFontSize.Value = ConverterDefaults.FontSize; + nudOffsetX.Value = ConverterDefaults.OffsetX; + nudBottomOffset.Value = ConverterDefaults.BottomOffset; + nudSbsSideMargin.Value = ConverterDefaults.SbsSideMargin; + nudOuTopMargin.Value = ConverterDefaults.OuTopMargin; + nudVerticalMargin.Value = ConverterDefaults.VerticalMargin; + + switch (ConverterDefaults.DefaultMode.ToLowerInvariant()) + { + case "sbs": + cmbMode.SelectedIndex = 0; + break; + case "ou": + cmbMode.SelectedIndex = 1; + break; + case "rg": + cmbMode.SelectedIndex = 2; + break; + default: + cmbMode.SelectedIndex = 0; + break; + } + } + private void btnBrowseInput_Click(object sender, EventArgs e) { using (var ofd = new OpenFileDialog()) @@ -84,7 +131,7 @@ namespace ConvertSRTto3DASSGUI string inputPath = txtInputFile.Text.Trim(); string outputPath = txtOutputFile.Text.Trim(); - string mode = cmbMode.SelectedItem?.ToString() ?? "sbs"; + string mode = GetSelectedModeValue(); if (string.IsNullOrWhiteSpace(inputPath)) { @@ -106,26 +153,43 @@ namespace ConvertSRTto3DASSGUI int resX = (int)nudResX.Value; int resY = (int)nudResY.Value; + int baseResX = (int)nudBaseResX.Value; + int baseResY = (int)nudBaseResY.Value; int fontSize = (int)nudFontSize.Value; int offsetX = (int)nudOffsetX.Value; + int bottomOffset = (int)nudBottomOffset.Value; + int sbsSideMargin = (int)nudSbsSideMargin.Value; + int ouTopMargin = (int)nudOuTopMargin.Value; + int verticalMargin = (int)nudVerticalMargin.Value; AppendStatus("Starting conversion..."); AppendStatus("Input: " + inputPath); AppendStatus("Output: " + outputPath); AppendStatus("Mode: " + mode); AppendStatus("Resolution: " + resX + "x" + resY); + AppendStatus("Base Resolution: " + baseResX + "x" + baseResY); AppendStatus("Font Size: " + fontSize); AppendStatus("OffsetX: " + offsetX); + AppendStatus("BottomOffset: " + bottomOffset); + AppendStatus("SbsSideMargin: " + sbsSideMargin); + AppendStatus("OuTopMargin: " + ouTopMargin); + AppendStatus("VerticalMargin: " + verticalMargin); - ConvertSRTto3DASS.Converter.Main(new[] + Converter.Main(new[] { - txtInputFile.Text, - "--mode", GetSelectedModeValue(), - "--resx", nudResX.Value.ToString(), - "--resy", nudResY.Value.ToString(), - "--fontsize", nudFontSize.Value.ToString(), - "--offsetx", nudOffsetX.Value.ToString(), - "--output", txtOutputFile.Text + inputPath, + "--mode", mode, + "--resx", resX.ToString(), + "--resy", resY.ToString(), + "--baseresx", baseResX.ToString(), + "--baseresy", baseResY.ToString(), + "--fontsize", fontSize.ToString(), + "--offsetx", offsetX.ToString(), + "--bottomoffset", bottomOffset.ToString(), + "--sbssidemargin", sbsSideMargin.ToString(), + "--outopmargin", ouTopMargin.ToString(), + "--verticalmargin", verticalMargin.ToString(), + "--output", outputPath }); AppendStatus("Conversion complete."); @@ -140,11 +204,26 @@ namespace ConvertSRTto3DASSGUI private void UpdateModeUI() { - string mode = cmbMode.SelectedItem?.ToString() ?? "sbs"; - bool isRg = mode.Equals("rg", StringComparison.OrdinalIgnoreCase); + string mode = GetSelectedModeValue(); + + bool isSbs = mode == "sbs"; + bool isOu = mode == "ou"; + bool isRg = mode == "rg"; nudOffsetX.Enabled = isRg; lblOffsetX.Enabled = isRg; + + nudBottomOffset.Enabled = isRg; + lblBottomOffset.Enabled = isRg; + + nudSbsSideMargin.Enabled = isSbs; + lblSbsSideMargin.Enabled = isSbs; + + nudOuTopMargin.Enabled = isOu; + lblOuTopMargin.Enabled = isOu; + + nudVerticalMargin.Enabled = true; + lblVerticalMargin.Enabled = true; } private void AppendStatus(string message) @@ -154,5 +233,9 @@ namespace ConvertSRTto3DASSGUI txtStatus.AppendText(message); } + + private void Form1_Load(object sender, EventArgs e) + { + } } } \ No newline at end of file From ea0574ffb23a6ff459f8423a1476285342746d2b Mon Sep 17 00:00:00 2001 From: kaiju466 Date: Mon, 15 Jun 2026 09:56:57 -0400 Subject: [PATCH 4/4] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index faf75f4..bae707b 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ Currently just a command line program to convert a .srt subtitle file to an .ass _Basically nothing right now_ - [x] Convert a .srt to an .ass/.ssa - [x] For HSBS media - - [ ] For **S**ide **B**y **S**ide (SBS) media - - [ ] For **O**ver **U**nder (OU) media - - [ ] For Anaglyph 3D media (red and green media) + - [X] For **S**ide **B**y **S**ide (SBS) media + - [X] For **O**ver **U**nder (OU) media + - [X] For Anaglyph 3D media (red and green media) - Warning Experimental - [ ] For traditionally media (pancake mode). - _I suggest to just use ffmpeg for this use case_ - [x] Convert formatting of .srt subtitles to transfer to the converted version @@ -20,9 +20,9 @@ _Basically nothing right now_ - [ ] Font - [ ] Color - [ ] Position - - [ ] Margins + - [X] Margins - [ ] Encoding -- [ ] Add a proper GUI +- [X] Add a proper GUI - [ ] Any form of error handling or unit testing