6 Commits

Author SHA1 Message Date
Imrayya 817b476c5f Update known issues in README
Remove mention of Plex Transcoder subtitle issues.
2026-06-26 00:32:35 +07:00
Imrayya 02176d0d7e Revise instructions in README
Updated CLI and GUI publishing instructions for self-contained builds on various platforms
Updated the CLI and GUI usage guide
2026-06-26 00:28:03 +07:00
Kareem Horstink 1c5d2e3934 fix: wire up Convert button Click handler (was never attached) 2026-06-25 17:04:40 +00:00
Kareem Horstink 9b7b8df874 fix: wire up GUI browse buttons, make text boxes editable, pause CLI on error, register codepage provider
GUI:
- Attach Click event handlers to browse input/output buttons (were defined but never wired up)
- Remove ReadOnly=true from input/output text boxes so users can type or copy paths

CLI:
- Make Cli.Run return bool to signal errors
- Only pause with Console.ReadLine() when an error occurs (not on success)
- Fix default case to return true

Core:
- Add System.Text.Encoding.CodePages package for CP1252 SRT file support
- Call Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) before reading files
2026-06-25 16:46:05 +00:00
Imrayya 40ce11d068 feat: modernize to .NET 8, split into Core/Cli/Gui (#3)
* feat: modernize to .NET 8, split into Core/Cli/Gui projects

Core: shared converter library (cross-platform)

Cli: console app with CLI args (cross-platform)

Gui: WinForms app (Windows only, uses Core library)

- Split into 3 projects sharing ConvertSRTto3DASS.Core

- Rewrite SrtConverter with clean public API

- Add CLI entry point with stdlib argument parsing

- Redesign GUI with GroupBox layout, flat buttons, modern fonts

- Update README with publish commands

* feat: add drag-and-drop to GUI

* refactor: rename GUI controls to proper PascalCase identifiers

---------

Co-authored-by: imrayya <imrayya@users.noreply.github.com>
2026-06-24 18:02:05 +07:00
imrayya 743900f2f7 chore: update to .NET 4.8, clean ownership, rewrite README
- Target .NET Framework 4.8 (both projects + App.config)
- Remove Stanley Black & Decker references, set ownership to imrayya
- Update copyright year to 2021-2026
- Remove stale project properties (SignAssembly, RepositoryType, etc.)
- Rewrite README: professional docs, usage guide, features, contributors, disclaimer
- Update LICENSE year range
2026-06-24 09:41:44 +00:00
28 changed files with 1072 additions and 1851 deletions
+153
View File
@@ -0,0 +1,153 @@
using System.Text;
namespace ConvertSRTto3DASS;
internal static class Cli
{
public static bool Run(string[] args)
{
if (args.Length == 0 || args[0] is "--help" or "-h" or "/?")
{
PrintUsage();
return false;
}
var inputPath = args[0];
if (!File.Exists(inputPath))
{
Console.Error.WriteLine($"Input file not found: {inputPath}");
return true;
}
// Parse options
var options = new ConversionOptions
{
InputPath = inputPath,
Mode = "sbs",
ResX = 1280,
ResY = 720,
BaseResX = 1280,
BaseResY = 720,
FontSize = 16,
OffsetX = 4,
BottomOffset = 18,
SbsSideMargin = 640,
OuTopMargin = 385,
VerticalMargin = 25,
};
int i = 1;
while (i < args.Length)
{
var arg = args[i].Trim().ToLowerInvariant();
switch (arg)
{
case "--mode":
options.Mode = GetNextArg(i++);
break;
case "--resx":
options.ResX = ParseInt(i++);
break;
case "--resy":
options.ResY = ParseInt(i++);
break;
case "--baseresx":
options.BaseResX = ParseInt(i++);
break;
case "--baseresy":
options.BaseResY = ParseInt(i++);
break;
case "--fontsize":
options.FontSize = ParseInt(i++);
break;
case "--offsetx":
options.OffsetX = ParseInt(i++);
break;
case "--bottomoffset":
options.BottomOffset = ParseInt(i++);
break;
case "--sbssidemargin":
options.SbsSideMargin = ParseInt(i++);
break;
case "--outopmargin":
options.OuTopMargin = ParseInt(i++);
break;
case "--verticalmargin":
options.VerticalMargin = ParseInt(i++);
break;
case "--output":
options.OutputPath = GetNextArg(i++);
break;
default:
Console.Error.WriteLine($"Unknown argument: {args[i]}");
return true;
}
i++;
}
// Default output path
if (string.IsNullOrEmpty(options.OutputPath))
{
options.OutputPath = Path.Combine(
Path.GetDirectoryName(options.InputPath) ?? ".",
Path.GetFileNameWithoutExtension(options.InputPath) + ".ass");
}
// Run conversion
try
{
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 resolution: {options.BaseResX}x{options.BaseResY}");
Console.WriteLine($"Font size: {options.FontSize}");
var result = SrtConverter.Convert(options);
Console.WriteLine($"Parsed: {result.SubtitleCount} subtitle blocks");
Console.WriteLine("Conversion complete.");
return false;
}
catch (Exception ex)
{
Console.Error.WriteLine($"ERROR: {ex.Message}");
Console.Error.WriteLine(ex.ToString());
return true;
}
string GetNextArg(int index) => index + 1 < args.Length ? args[index + 1] : throw new ArgumentException($"Missing value for {args[index]}");
int ParseInt(int index)
{
var val = index + 1 < args.Length ? args[index + 1] : throw new ArgumentException($"Missing value for {args[index]}");
return int.TryParse(val, out var n) ? n : throw new ArgumentException($"Invalid value for {args[index]}: {val}");
}
}
static void PrintUsage()
{
Console.WriteLine("Usage:");
Console.WriteLine(" ConvertSRTto3DASS.exe <input.srt> [options]");
Console.WriteLine();
Console.WriteLine("Options:");
Console.WriteLine(" --mode sbs|ou|rg");
Console.WriteLine(" --resx <number>");
Console.WriteLine(" --resy <number>");
Console.WriteLine(" --baseresx <number> (scaling reference width, default 1280)");
Console.WriteLine(" --baseresy <number> (scaling reference height, default 720)");
Console.WriteLine(" --fontsize <number>");
Console.WriteLine(" --offsetx <number> (RG eye separation)");
Console.WriteLine(" --bottomoffset <number> (RG bottom offset)");
Console.WriteLine(" --sbssidemargin <number> (SBS side margin)");
Console.WriteLine(" --outopmargin <number> (OU top subtitle margin)");
Console.WriteLine(" --verticalmargin <number> (general vertical/bottom margin)");
Console.WriteLine(" --output <path>");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs");
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""");
}
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>imrayya</Company>
<Copyright>Copyright © imrayya 2021-2026</Copyright>
<AssemblyName>ConvertSRTto3DASS</AssemblyName>
<RootNamespace>ConvertSRTto3DASS</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ConvertSRTto3DASS.Core\ConvertSRTto3DASS.Core.csproj" />
</ItemGroup>
</Project>
+11
View File
@@ -0,0 +1,11 @@
namespace ConvertSRTto3DASS;
internal static class Program
{
static void Main(string[] args)
{
var hadError = Cli.Run(args);
if (hadError)
Console.ReadLine();
}
}
@@ -0,0 +1,21 @@
namespace ConvertSRTto3DASS;
public class ConversionOptions
{
public string InputPath { get; set; } = "";
public string OutputPath { get; set; } = "";
public string Mode { get; set; } = "sbs"; // sbs, ou, rg
public int ResX { get; set; } = 1280;
public int ResY { get; set; } = 720;
public int BaseResX { get; set; } = 1280;
public int BaseResY { get; set; } = 720;
public int FontSize { get; set; } = 16;
public int OffsetX { get; set; } = 4;
public int BottomOffset { get; set; } = 18;
public int SbsSideMargin { get; set; } = 640;
public int OuTopMargin { get; set; } = 385;
public int VerticalMargin { get; set; } = 25;
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>imrayya</Company>
<Copyright>Copyright © imrayya 2021-2026</Copyright>
<RootNamespace>ConvertSRTto3DASS</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>
</Project>
+276
View File
@@ -0,0 +1,276 @@
using System.Text;
using System.Text.RegularExpressions;
namespace ConvertSRTto3DASS;
/// <summary>
/// Core SRT-to-ASS conversion logic for 3D subtitle formats.
/// </summary>
public static class SrtConverter
{
public class ConversionResult
{
public string OutputText { get; set; } = "";
public int SubtitleCount { get; set; }
}
public static ConversionResult Convert(ConversionOptions options)
{
// Register legacy codepage support (needed for CP1252, etc.)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var srtText = ReadSrt(options.InputPath);
var subtitles = ParseSrt(srtText);
var header = CreateHeader(options);
var styles = CreateStyles(options);
var events = GenerateEvents(subtitles, options);
var output =
"[Script Info]\n" +
header +
"\n\n[V4+ Styles]\n" +
styles +
"\n\n[Events]\n" +
events;
File.WriteAllText(options.OutputPath, output, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
return new ConversionResult
{
OutputText = output,
SubtitleCount = subtitles.Count
};
}
#region SRT Parsing
private static string ReadSrt(string path)
{
using var reader = new StreamReader(path, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
var text = reader.ReadToEnd();
// Fallback to Windows-1252 if we see replacement characters
if (text.Contains('\uFFFD'))
text = File.ReadAllText(path, Encoding.GetEncoding(1252));
return text;
}
private static List<SrtSubtitle> ParseSrt(string srtText)
{
var results = new List<SrtSubtitle>();
srtText = srtText.Replace("\r\n", "\n").Replace("\r", "\n").Trim();
var blocks = Regex.Split(srtText, @"\n\s*\n");
int expectedNumber = 1;
foreach (var block in blocks)
{
var lines = block.Split('\n', StringSplitOptions.TrimEntries)
.Where(l => l.Length > 0)
.ToList();
if (lines.Count < 3) continue;
if (!int.TryParse(lines[0], out var number)) continue;
if (number != expectedNumber)
{
Console.Error.WriteLine($"Warning: expected subtitle number {expectedNumber}, but found {number}.");
expectedNumber = number;
}
expectedNumber++;
var timeMatch = Regex.Match(lines[1],
@"^(?<start>\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(?<end>\d{2}:\d{2}:\d{2},\d{3})$");
if (!timeMatch.Success)
{
Console.Error.WriteLine($"Skipping malformed timestamp: [{lines[1]}]");
continue;
}
var text = string.Join("\\N", lines.Skip(2));
text = NormalizeFormatting(text);
results.Add(new SrtSubtitle
{
Start = timeMatch.Groups["start"].Value,
End = timeMatch.Groups["end"].Value,
Text = text
});
}
return results;
}
#endregion
#region Formatting
private static readonly Dictionary<Regex, string> _tagReplacements = new()
{
{ new Regex("<b>", RegexOptions.IgnoreCase), "{\\b1}" },
{ new Regex("</b>", RegexOptions.IgnoreCase), "{\\b0}" },
{ new Regex("<i>", RegexOptions.IgnoreCase), "{\\i1}" },
{ new Regex("</i>", RegexOptions.IgnoreCase), "{\\i0}" },
{ new Regex("<u>", RegexOptions.IgnoreCase), "{\\u1}" },
{ new Regex("</u>", RegexOptions.IgnoreCase), "{\\u0}" },
{ new Regex("</font>", RegexOptions.IgnoreCase), "{\\c&HFFFFFF&}" }
};
private static readonly Regex _colorRegex =
new Regex("<font color=\"#([0-9A-Fa-f]{6})\">", RegexOptions.IgnoreCase);
private static readonly Regex _markupRegex =
new Regex("<.+?>|(\\r)", RegexOptions.IgnoreCase);
private static string NormalizeFormatting(string text)
{
foreach (var (pattern, replacement) in _tagReplacements)
text = pattern.Replace(text, replacement);
// Convert HTML color to ASS BBGGRR format
while (_colorRegex.IsMatch(text))
{
var match = _colorRegex.Match(text);
var rgb = match.Groups[1].Value;
var assColor = rgb.Substring(4, 2) + rgb.Substring(2, 2) + rgb.Substring(0, 2);
text = _colorRegex.Replace(text, "{\\c&H" + assColor + "&}", 1);
}
return _markupRegex.Replace(text, "");
}
#endregion
#region Time & Scaling
private static string ConvertTimestamp(string timestamp)
{
var tmp = timestamp.Substring(1);
tmp = tmp.Substring(0, tmp.Length - 1);
return tmp.Replace(",", ".");
}
private static int ScaleX(int value, ConversionOptions o) =>
(int)Math.Round(value * (o.ResX / (double)o.BaseResX));
private static int ScaleY(int value, ConversionOptions o) =>
(int)Math.Round(value * (o.ResY / (double)o.BaseResY));
private static int ScaleFont(int fontSize, ConversionOptions o) =>
Math.Max(1, ScaleY(fontSize, o));
#endregion
#region ASS Output Generation
private static string CreateHeader(ConversionOptions o) =>
$"; Generated by SRTto3Dsubtitles\n" +
$"Title: {Path.GetFileNameWithoutExtension(o.InputPath)}\n" +
"ScriptType: v4.00+\n" +
"Collisions: Normal\n" +
$"PlayResX: {o.ResX}\n" +
$"PlayResY: {o.ResY}\n" +
"ScaledBorderAndShadow: yes";
private static string CreateStyles(ConversionOptions o)
{
var fontSize = ScaleFont(o.FontSize, o);
return o.Mode.ToLowerInvariant() switch
{
"ou" => CreateOuStyles(fontSize, o),
"rg" => CreateRgStyles(fontSize, o),
_ => CreateSbsStyles(fontSize, o)
};
}
private static string CreateSbsStyles(int fontSize, ConversionOptions o)
{
var sideMargin = ScaleX(o.SbsSideMargin, o);
var verticalMargin = ScaleY(o.VerticalMargin, o);
return FormatStyle("Right", fontSize, sideMargin, 0, verticalMargin) + "\n" +
FormatStyle("Left", fontSize, 0, sideMargin, verticalMargin);
}
private static string CreateOuStyles(int fontSize, ConversionOptions o)
{
var topMargin = ScaleY(o.OuTopMargin, o);
var bottomMargin = ScaleY(o.VerticalMargin, o);
return FormatStyle("Top", fontSize, 0, 0, topMargin) + "\n" +
FormatStyle("Bottom", fontSize, 0, 0, bottomMargin);
}
private static string CreateRgStyles(int fontSize, ConversionOptions o)
{
var verticalMargin = ScaleY(o.VerticalMargin, o);
// Red = &H0000FF, Green = &H00FF00 (ASS uses BBGGRR)
return FormatStyle("RedEye", fontSize, 0, 0, verticalMargin, primary: "&H0000FF") + "\n" +
FormatStyle("GreenEye", fontSize, 0, 0, verticalMargin, primary: "&H00FF00");
}
private static string FormatStyle(string name, int fontSize, int marginL, int marginR, int marginV,
string primary = "&HFFFFFF")
{
return $"Style: {name},Arial,{fontSize},{primary},{primary},&H000000,&H000000," +
$"0,0,0,0,100,100,0,0,1,1,0,2," +
$"{marginL},{marginR},{marginV},0";
}
private static string GenerateEvents(List<SrtSubtitle> subs, ConversionOptions o)
{
var sb = new StringBuilder();
sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
foreach (var sub in subs)
{
var start = ConvertTimestamp(sub.Start);
var end = ConvertTimestamp(sub.End);
var text = sub.Text;
switch (o.Mode.ToLowerInvariant())
{
case "sbs":
sb.AppendLine($"Dialogue: 0,{start},{end},Right,,0,0,0,,{text}");
sb.AppendLine($"Dialogue: 1,{start},{end},Left,,0,0,0,,{text}");
break;
case "ou":
sb.AppendLine($"Dialogue: 0,{start},{end},Top,,0,0,0,,{text}");
sb.AppendLine($"Dialogue: 1,{start},{end},Bottom,,0,0,0,,{text}");
break;
case "rg":
var centerX = o.ResX / 2;
var scaledBottomOffset = ScaleY(o.BottomOffset, o);
var y = o.ResY - scaledBottomOffset;
var scaledOffsetX = ScaleX(o.OffsetX, o);
var redX = centerX - scaledOffsetX;
var greenX = centerX + scaledOffsetX;
sb.AppendLine($"Dialogue: 0,{start},{end},RedEye,,0,0,0,,{{\\an2\\pos({redX},{y})}}{text}");
sb.AppendLine($"Dialogue: 1,{start},{end},GreenEye,,0,0,0,,{{\\an2\\pos({greenX},{y})}}{text}");
break;
}
}
return sb.ToString();
}
#endregion
#region Data Classes
private class SrtSubtitle
{
public string Start { get; set; } = "";
public string End { get; set; } = "";
public string Text { get; set; } = "";
}
#endregion
}
@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>imrayya</Company>
<Copyright>Copyright © imrayya 2021-2026</Copyright>
<AssemblyName>ConvertSRTto3DASS</AssemblyName>
<RootNamespace>ConvertSRTto3DASS</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ConvertSRTto3DASS.Core\ConvertSRTto3DASS.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
+201
View File
@@ -0,0 +1,201 @@
namespace ConvertSRTto3DASS;
partial class Form1
{
private System.ComponentModel.IContainer components = null;
private Label label_Title;
private GroupBox groupBox_File;
private Label label_InputFile;
private TextBox textBox_InputFile;
private Button button_BrowseInput;
private Label label_OutputFile;
private TextBox textBox_OutputFile;
private Button button_BrowseOutput;
private GroupBox groupBox_Mode;
private Label label_Mode;
private ComboBox comboBox_Mode;
private GroupBox groupBox_Resolution;
private Label label_ResX;
private NumericUpDown numericUpDown_ResX;
private Label label_ResY;
private NumericUpDown numericUpDown_ResY;
private Label label_BaseResX;
private NumericUpDown numericUpDown_BaseResX;
private Label label_BaseResY;
private NumericUpDown numericUpDown_BaseResY;
private GroupBox groupBox_Settings;
private Label label_FontSize;
private NumericUpDown numericUpDown_FontSize;
private Label label_OffsetX;
private NumericUpDown numericUpDown_OffsetX;
private Label label_BottomOffset;
private NumericUpDown numericUpDown_BottomOffset;
private Label label_SbsSideMargin;
private NumericUpDown numericUpDown_SbsSideMargin;
private Label label_OuTopMargin;
private NumericUpDown numericUpDown_OuTopMargin;
private Label label_VerticalMargin;
private NumericUpDown numericUpDown_VerticalMargin;
private Button button_Convert;
private TextBox textBox_Status;
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
var segoe = new Font("Segoe UI", 9f);
SuspendLayout();
// ---- Title ----
label_Title = new Label
{
Text = "SRT to 3D Subtitles",
Font = new Font("Segoe UI", 14f, FontStyle.Bold),
ForeColor = Color.FromArgb(30, 30, 30),
Location = new Point(20, 15),
AutoSize = true
};
// ---- File section ----
groupBox_File = new GroupBox
{
Text = "Files",
Font = segoe,
Location = new Point(20, 60),
Size = new Size(1040, 80)
};
label_InputFile = new Label { Text = "Input SRT:", Font = segoe, Location = new Point(15, 25), AutoSize = true };
textBox_InputFile = new TextBox { Location = new Point(100, 22), Size = new Size(780, 23) };
button_BrowseInput = new Button { Text = "Browse...", Location = new Point(890, 20), Size = new Size(90, 25), Font = segoe, FlatStyle = FlatStyle.Flat };
button_BrowseInput.Click += button_BrowseInput_Click;
label_OutputFile = new Label { Text = "Output ASS:", Font = segoe, Location = new Point(15, 55), AutoSize = true };
textBox_OutputFile = new TextBox { Location = new Point(100, 52), Size = new Size(780, 23) };
button_BrowseOutput = new Button { Text = "Browse...", Location = new Point(890, 50), Size = new Size(90, 25), Font = segoe, FlatStyle = FlatStyle.Flat };
button_BrowseOutput.Click += button_BrowseOutput_Click;
groupBox_File.Controls.AddRange([label_InputFile, textBox_InputFile, button_BrowseInput, label_OutputFile, textBox_OutputFile, button_BrowseOutput]);
// ---- Mode section ----
groupBox_Mode = new GroupBox
{
Text = "3D Mode",
Font = segoe,
Location = new Point(20, 150),
Size = new Size(320, 60)
};
label_Mode = new Label { Text = "Format:", Font = segoe, Location = new Point(15, 22), AutoSize = true };
comboBox_Mode = new ComboBox
{
Location = new Point(85, 19),
Size = new Size(210, 25),
Font = segoe,
DropDownStyle = ComboBoxStyle.DropDownList
};
comboBox_Mode.Items.AddRange(["SBS (Side-by-Side)", "OU (Over-Under)", "RG (Red-Green)"]);
comboBox_Mode.SelectedIndexChanged += comboBox_Mode_SelectedIndexChanged;
groupBox_Mode.Controls.AddRange([label_Mode, comboBox_Mode]);
// ---- Resolution section ----
groupBox_Resolution = new GroupBox
{
Text = "Resolution",
Font = segoe,
Location = new Point(355, 150),
Size = new Size(320, 60)
};
label_ResX = new Label { Text = "Output:", Font = segoe, Location = new Point(15, 22), AutoSize = true };
numericUpDown_ResX = new NumericUpDown { Location = new Point(65, 20), Size = new Size(80, 23), Minimum = 1, Maximum = 10000, Value = 1280 };
label_ResY = new Label { Text = "x", Font = segoe, Location = new Point(150, 22), AutoSize = true };
numericUpDown_ResY = new NumericUpDown { Location = new Point(165, 20), Size = new Size(80, 23), Minimum = 1, Maximum = 10000, Value = 720 };
label_BaseResX = new Label { Text = "Base:", Font = segoe, Location = new Point(255, 22), AutoSize = true };
numericUpDown_BaseResX = new NumericUpDown { Location = new Point(290, 20), Size = new Size(80, 23), Minimum = 1, Maximum = 10000, Value = 1280 };
label_BaseResY = new Label { Text = "x", Font = segoe, Location = new Point(375, 22), AutoSize = true };
numericUpDown_BaseResY = new NumericUpDown { Location = new Point(390, 20), Size = new Size(80, 23), Minimum = 1, Maximum = 10000, Value = 720 };
groupBox_Resolution.Controls.AddRange([label_ResX, numericUpDown_ResX, label_ResY, numericUpDown_ResY, label_BaseResX, numericUpDown_BaseResX, label_BaseResY, numericUpDown_BaseResY]);
// ---- Settings section ----
groupBox_Settings = new GroupBox
{
Text = "Settings",
Font = segoe,
Location = new Point(20, 220),
Size = new Size(1040, 120)
};
var col1 = 15;
var col2 = 160;
var col3 = 310;
var col4 = 460;
var col5 = 610;
var col6 = 760;
var row = 25;
var rowStep = 30;
label_FontSize = new Label { Text = "Font Size:", Font = segoe, Location = new Point(col1, row), AutoSize = true };
numericUpDown_FontSize = new NumericUpDown { Location = new Point(col2, row - 2), Size = new Size(80, 23), Minimum = 1, Maximum = 200, Value = 16 };
label_OffsetX = new Label { Text = "Offset X:", Font = segoe, Location = new Point(col3, row), AutoSize = true };
numericUpDown_OffsetX = new NumericUpDown { Location = new Point(col4, row - 2), Size = new Size(80, 23), Minimum = 0, Maximum = 1000, Value = 4 };
label_BottomOffset = new Label { Text = "Bottom Offset:", Font = segoe, Location = new Point(col5, row), AutoSize = true };
numericUpDown_BottomOffset = new NumericUpDown { Location = new Point(col6, row - 2), Size = new Size(80, 23), Minimum = 0, Maximum = 1000, Value = 18 };
row += rowStep;
label_SbsSideMargin = new Label { Text = "SBS Margin:", Font = segoe, Location = new Point(col1, row), AutoSize = true };
numericUpDown_SbsSideMargin = new NumericUpDown { Location = new Point(col2, row - 2), Size = new Size(80, 23), Minimum = 0, Maximum = 5000, Value = 640 };
label_OuTopMargin = new Label { Text = "OU Margin:", Font = segoe, Location = new Point(col3, row), AutoSize = true };
numericUpDown_OuTopMargin = new NumericUpDown { Location = new Point(col4, row - 2), Size = new Size(80, 23), Minimum = 0, Maximum = 5000, Value = 385 };
label_VerticalMargin = new Label { Text = "V Margin:", Font = segoe, Location = new Point(col5, row), AutoSize = true };
numericUpDown_VerticalMargin = new NumericUpDown { Location = new Point(col6, row - 2), Size = new Size(80, 23), Minimum = 0, Maximum = 5000, Value = 25 };
groupBox_Settings.Controls.AddRange([
label_FontSize, numericUpDown_FontSize, label_OffsetX, numericUpDown_OffsetX, label_BottomOffset, numericUpDown_BottomOffset,
label_SbsSideMargin, numericUpDown_SbsSideMargin, label_OuTopMargin, numericUpDown_OuTopMargin, label_VerticalMargin, numericUpDown_VerticalMargin
]);
// ---- Convert button ----
button_Convert = new Button
{
Text = "Convert",
Font = new Font("Segoe UI", 10f, FontStyle.Bold),
Location = new Point(20, 355),
Size = new Size(140, 36),
FlatStyle = FlatStyle.Flat,
BackColor = Color.FromArgb(0, 120, 215),
ForeColor = Color.White,
FlatAppearance = { BorderSize = 0 }
};
button_Convert.Click += button_Convert_Click;
// ---- Status ----
textBox_Status = new TextBox
{
Location = new Point(20, 405),
Size = new Size(1040, 180),
Multiline = true,
ReadOnly = true,
ScrollBars = ScrollBars.Vertical,
Font = new Font("Consolas", 8.5f),
BackColor = Color.FromArgb(245, 245, 245)
};
// ---- Form ----
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1080, 610);
MinimumSize = new Size(1100, 650);
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
AllowDrop = true;
Text = "SRT to 3D Subtitles";
Font = segoe;
StartPosition = FormStartPosition.CenterScreen;
Controls.AddRange([label_Title, groupBox_File, groupBox_Mode, groupBox_Resolution, groupBox_Settings, button_Convert, textBox_Status]);
ResumeLayout(false);
PerformLayout();
}
}
+170
View File
@@ -0,0 +1,170 @@
using System.IO;
namespace ConvertSRTto3DASS;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox_Mode.SelectedIndex = 0;
UpdateModeUI();
// Drag-and-drop
DragEnter += (s, e) =>
{
if (e.Data!.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
};
DragDrop += (s, e) =>
{
var files = (string[])e.Data!.GetData(DataFormats.FileDrop)!;
if (files.Length > 0 && files[0].EndsWith(".srt", StringComparison.OrdinalIgnoreCase))
{
textBox_InputFile.Text = files[0];
if (string.IsNullOrWhiteSpace(textBox_OutputFile.Text))
textBox_OutputFile.Text = Path.ChangeExtension(files[0], ".ass");
AppendStatus("Dropped: " + files[0]);
}
};
}
private void button_BrowseInput_Click(object sender, EventArgs e)
{
using var ofd = new OpenFileDialog
{
Filter = "SRT files (*.srt)|*.srt|All files (*.*)|*.*",
Title = "Select input SRT file"
};
if (ofd.ShowDialog() == DialogResult.OK)
{
textBox_InputFile.Text = ofd.FileName;
if (string.IsNullOrWhiteSpace(textBox_OutputFile.Text))
textBox_OutputFile.Text = Path.ChangeExtension(ofd.FileName, ".ass");
AppendStatus("Selected input file: " + ofd.FileName);
}
}
private void button_BrowseOutput_Click(object sender, EventArgs e)
{
using var sfd = new SaveFileDialog
{
Filter = "ASS files (*.ass)|*.ass|All files (*.*)|*.*",
Title = "Select output ASS file",
DefaultExt = "ass"
};
if (!string.IsNullOrWhiteSpace(textBox_InputFile.Text))
{
sfd.FileName = Path.GetFileNameWithoutExtension(textBox_InputFile.Text) + ".ass";
sfd.InitialDirectory = Path.GetDirectoryName(textBox_InputFile.Text);
}
if (sfd.ShowDialog() == DialogResult.OK)
{
textBox_OutputFile.Text = sfd.FileName;
AppendStatus("Selected output file: " + sfd.FileName);
}
}
private void comboBox_Mode_SelectedIndexChanged(object sender, EventArgs e) => UpdateModeUI();
private string GetSelectedModeValue()
{
var selected = comboBox_Mode.SelectedItem?.ToString() ?? "sbs";
var spaceIndex = selected.IndexOf(' ');
return spaceIndex > 0 ? selected[..spaceIndex].Trim().ToLowerInvariant() : selected.Trim().ToLowerInvariant();
}
private void button_Convert_Click(object sender, EventArgs e)
{
try
{
textBox_Status.Clear();
var inputPath = textBox_InputFile.Text.Trim();
var outputPath = textBox_OutputFile.Text.Trim();
var mode = GetSelectedModeValue();
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");
textBox_OutputFile.Text = outputPath;
}
var options = new ConversionOptions
{
InputPath = inputPath,
OutputPath = outputPath,
Mode = mode,
ResX = (int)numericUpDown_ResX.Value,
ResY = (int)numericUpDown_ResY.Value,
BaseResX = (int)numericUpDown_BaseResX.Value,
BaseResY = (int)numericUpDown_BaseResY.Value,
FontSize = (int)numericUpDown_FontSize.Value,
OffsetX = (int)numericUpDown_OffsetX.Value,
BottomOffset = (int)numericUpDown_BottomOffset.Value,
SbsSideMargin = (int)numericUpDown_SbsSideMargin.Value,
OuTopMargin = (int)numericUpDown_OuTopMargin.Value,
VerticalMargin = (int)numericUpDown_VerticalMargin.Value
};
AppendStatus("Starting conversion...");
AppendStatus($"Input: {options.InputPath}");
AppendStatus($"Output: {options.OutputPath}");
AppendStatus($"Mode: {options.Mode}");
AppendStatus($"Resolution: {options.ResX}x{options.ResY}");
AppendStatus($"Base resolution: {options.BaseResX}x{options.BaseResY}");
AppendStatus($"Font size: {options.FontSize}");
var result = SrtConverter.Convert(options);
AppendStatus($"Parsed: {result.SubtitleCount} subtitle blocks");
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()
{
var mode = GetSelectedModeValue();
numericUpDown_OffsetX.Enabled = mode == "rg";
label_OffsetX.Enabled = mode == "rg";
numericUpDown_BottomOffset.Enabled = mode == "rg";
label_BottomOffset.Enabled = mode == "rg";
numericUpDown_SbsSideMargin.Enabled = mode == "sbs";
label_SbsSideMargin.Enabled = mode == "sbs";
numericUpDown_OuTopMargin.Enabled = mode == "ou";
label_OuTopMargin.Enabled = mode == "ou";
}
private void AppendStatus(string message)
{
if (textBox_Status.TextLength > 0)
textBox_Status.AppendText(Environment.NewLine);
textBox_Status.AppendText(message);
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace ConvertSRTto3DASS;
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
@@ -8,7 +8,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace ConvertSRTto3DASSGUI.Properties namespace ConvertSRTto3DASS.Properties
{ {
@@ -8,7 +8,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace ConvertSRTto3DASSGUI.Properties namespace ConvertSRTto3DASS.Properties
{ {
+18 -18
View File
@@ -1,11 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.11.35208.52 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertSRTto3DASS", "ConvertSRTto3DASS\ConvertSRTto3DASS.csproj", "{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertSRTto3DASS.Core", "ConvertSRTto3DASS.Core\ConvertSRTto3DASS.Core.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConvertSRTto3DASSGUI", "ConvertSRTto3DASSGUI\ConvertSRTto3DASSGUI.csproj", "{8C44F434-2097-4FA3-BEE1-9569FB99F49C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertSRTto3DASS.Cli", "ConvertSRTto3DASS.Cli\ConvertSRTto3DASS.Cli.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConvertSRTto3DASS.Gui", "ConvertSRTto3DASS.Gui\ConvertSRTto3DASS.Gui.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,19 +15,17 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Release|Any CPU.Build.0 = Release|Any CPU {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C44F434-2097-4FA3-BEE1-9569FB99F49C}.Release|Any CPU.Build.0 = Release|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
GlobalSection(SolutionProperties) = preSolution {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
HideSolutionNode = FALSE {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection {C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5E10CFB-7432-4C4B-80FB-1B9349811932}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
@@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net46</TargetFramework>
<StartupObject>ConvertSRTto3DASS.Converter</StartupObject>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>Git</RepositoryType>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>
-799
View File
@@ -1,799 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
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
namespace ConvertSRTto3DASS
{
public class Converter
{
private enum StereoMode
{
SBS,
OU,
RG // Experimental
}
public static class ConverterDefaults
{
/// <summary>
///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).
/// </summary>
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; } = 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<Regex, string> RegexReplacementDict =
new Dictionary<Regex, string>
{
{ new Regex("<b>", RegexOptions.IgnoreCase), "{\\b1}" },
{ new Regex("</b>", RegexOptions.IgnoreCase), "{\\b0}" },
{ new Regex("<i>", RegexOptions.IgnoreCase), "{\\i1}" },
{ new Regex("</i>", RegexOptions.IgnoreCase), "{\\i0}" },
{ new Regex("<u>", RegexOptions.IgnoreCase), "{\\u1}" },
{ new Regex("</u>", RegexOptions.IgnoreCase), "{\\u0}" },
{ new Regex("</font>", RegexOptions.IgnoreCase), "{\\c&HFFFFFF&}" }
};
private static readonly Regex ColorRegex =
new Regex("<font color=\"#([0-9A-Fa-f]{6})\">", RegexOptions.IgnoreCase);
private static readonly Regex RemoveFormattingRegex =
new Regex("<.+?>|(\\r)", RegexOptions.IgnoreCase);
public static void Main(string[] args)
{
try
{
var options = ParseArguments(args);
if (options == null)
return;
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) ?? 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("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);
var header = CreateHeader(options.InputPath, options.ResY, options.ResX);
var eventsText = ProcessSubs(extracted, options);
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;
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 "--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 "--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 "/?":
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 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 <input.srt> [options]");
Console.WriteLine();
Console.WriteLine("Options:");
Console.WriteLine(" --mode sbs|ou|rg");
Console.WriteLine(" --resx <number>");
Console.WriteLine(" --resy <number>");
Console.WriteLine(" --baseresx <number> (scaling reference width, default 1280)");
Console.WriteLine(" --baseresy <number> (scaling reference height, default 720)");
Console.WriteLine(" --fontsize <number>");
Console.WriteLine(" --offsetx <number> (RG eye separation)");
Console.WriteLine(" --bottomoffset <number> (RG bottom offset)");
Console.WriteLine(" --sbssidemargin <number> (SBS side margin)");
Console.WriteLine(" --outopmargin <number> (OU top subtitle margin)");
Console.WriteLine(" --verticalmargin <number> (general vertical/bottom margin)");
Console.WriteLine(" --output <path>");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(@" ConvertSRTto3DASS.exe ""movie.srt"" --mode sbs");
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)
{
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;
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, rg");
}
}
private static int ScaleX(int value, Options options)
{
return (int)Math.Round(value * (options.ResX / (double)options.BaseResX));
}
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)
{
line = tuple.Key.Replace(line, tuple.Value);
}
while (ColorRegex.IsMatch(line))
{
var match = ColorRegex.Match(line);
string rgb = match.Groups[1].Value; // RRGGBB
string rr = rgb.Substring(0, 2);
string gg = rgb.Substring(2, 2);
string bb = rgb.Substring(4, 2);
// ASS expects BBGGRR
string assColor = bb + gg + rr;
line = ColorRegex.Replace(line, "{\\c&H" + assColor + "&}", 1);
}
return RemoveFormatting(line);
}
private static string RemoveFormatting(string line)
{
return RemoveFormattingRegex.Replace(line, "");
}
private static string ProcessSubs(List<Tuple<string, string, string, string>> srt, Options options)
{
string endResult = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n";
foreach (var events in srt)
{
var start = ConvertTimeStamp(events.Item1);
var end = ConvertTimeStamp(events.Item2);
var text = 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 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;
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;
}
private static string ConvertTimeStamp(string timeStamp)
{
var tmp = timeStamp.Substring(1);
tmp = tmp.Substring(0, tmp.Length - 1);
return tmp.Replace(",", ".");
}
private static string CreateStandardStyle(Options options)
{
int scaledFontSize = ScaleFont(options.FontSize, options);
switch (options.Mode)
{
case StereoMode.SBS:
return CreateSbsStyle(scaledFontSize, options);
case StereoMode.OU:
return CreateOuStyle(scaledFontSize, options);
case StereoMode.RG:
return CreateRgStyle(scaledFontSize, options);
default:
throw new ArgumentOutOfRangeException(nameof(options.Mode));
}
}
private static string CreateSbsStyle(int fontSize, Options options)
{
int sideMargin = ScaleX(options.SbsSideMargin, options);
int verticalMargin = ScaleY(options.VerticalMargin, options);
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: " +
"Right," +
"Arial," +
fontSize + "," +
"&HFFFFFF," +
"&HFFFFFF," +
"&H000000," +
"&H000000," +
"0," +
"0," +
"0," +
"0," +
"100," +
"100," +
"0," +
"0," +
"1," +
"1," +
"0," +
"2," +
sideMargin + "," +
"0," +
verticalMargin + "," +
"0\n" +
"Style: " +
"Left," +
"Arial," +
fontSize + "," +
"&HFFFFFF," +
"&HFFFFFF," +
"&H000000," +
"&H000000," +
"0," +
"0," +
"0," +
"0," +
"100," +
"100," +
"0," +
"0," +
"1," +
"1," +
"0," +
"2," +
"0," +
sideMargin + "," +
verticalMargin + "," +
"0";
return style;
}
private static string CreateOuStyle(int fontSize, Options options)
{
int bottomMargin = ScaleY(options.VerticalMargin, options);
int topMargin = ScaleY(options.OuTopMargin, options);
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," +
"&H000000," +
"&H000000," +
"0," +
"0," +
"0," +
"0," +
"100," +
"100," +
"0," +
"0," +
"1," +
"1," +
"0," +
"2," +
"0," +
"0," +
topMargin + "," +
"0\n" +
"Style: " +
"Bottom," +
"Arial," +
fontSize + "," +
"&HFFFFFF," +
"&HFFFFFF," +
"&H000000," +
"&H000000," +
"0," +
"0," +
"0," +
"0," +
"100," +
"100," +
"0," +
"0," +
"1," +
"1," +
"0," +
"2," +
"0," +
"0," +
bottomMargin + "," +
"0";
return style;
}
private static string CreateRgStyle(int fontSize, Options options)
{
int verticalMargin = ScaleY(options.VerticalMargin, options);
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," +
verticalMargin + "," +
"0\n" +
// Green = &H00FF00
"Style: " +
"GreenEye," +
"Arial," +
fontSize + "," +
"&H00FF00," +
"&H00FF00," +
"&H000000," +
"&H000000," +
"0," +
"0," +
"0," +
"0," +
"100," +
"100," +
"0," +
"0," +
"1," +
"1," +
"0," +
"2," +
"0," +
"0," +
verticalMargin + "," +
"0";
return style;
}
private static string CreateHeader(string file, int resY = 720, int resX = 1280)
{
var name = Path.GetFileNameWithoutExtension(file);
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)
{
using (var reader = new StreamReader(path, Encoding.UTF8, detectEncodingFromByteOrderMarks: true))
{
string text = reader.ReadToEnd();
if (text.Contains('\uFFFD'))
text = File.ReadAllText(path, Encoding.GetEncoding(1252));
return text;
}
}
private static List<Tuple<string, string, string, string>> ExtractSubFromSRT(string file)
{
var results = new List<Tuple<string, string, string, string>>();
string srt = ReadTextSmart(file);
srt = srt.Replace("\r\n", "\n").Replace("\r", "\n");
var blocks = Regex.Split(srt.Trim(), @"\n\s*\n");
int expectedDialogNumber = 1;
foreach (var block in blocks)
{
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))
{
Console.Error.WriteLine($"Skipping malformed block: expected subtitle number, got [{lines[0]}]");
continue;
}
if (dialogNumber != expectedDialogNumber)
{
Console.Error.WriteLine(
$"Warning: expected subtitle number {expectedDialogNumber}, but found {dialogNumber}.");
expectedDialogNumber = dialogNumber;
}
expectedDialogNumber++;
var timeMatch = Regex.Match(
lines[1],
@"^(?<start>\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(?<end>\d{2}:\d{2}:\d{2},\d{3})$");
if (!timeMatch.Success)
{
Console.Error.WriteLine($"Skipping malformed timestamp line: [{lines[1]}]");
continue;
}
string timestampStart = timeMatch.Groups["start"].Value;
string timestampEnd = timeMatch.Groups["end"].Value;
string subtitleText = string.Join("\\N", lines.Skip(2));
results.Add(new Tuple<string, string, string, string>(
timestampStart,
timestampEnd,
ChangeFormatting(subtitleText),
""));
}
return results;
}
}
}
-26
View File
@@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ConvertSRTto3DASS.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.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;
}
}
}
}
@@ -1,6 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
</SettingsFile>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8C44F434-2097-4FA3-BEE1-9569FB99F49C}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>ConvertSRTto3DASSGUI</RootNamespace>
<AssemblyName>ConvertSRTto3DASSGUI</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ConvertSRTto3DASS\ConvertSRTto3DASS.csproj">
<Project>{7d7925de-adbd-4a26-b12b-8f12b68d3bfe}</Project>
<Name>ConvertSRTto3DASS</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
-551
View File
@@ -1,551 +0,0 @@
namespace ConvertSRTto3DASSGUI
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
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 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;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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.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
//
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[] {
1280,
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[] {
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});
//
// 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 = 16;
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 = 17;
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 = 18;
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 = 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, 329);
this.btnConvert.Name = "btnConvert";
this.btnConvert.Size = new System.Drawing.Size(145, 40);
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, 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 = 29;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
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);
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";
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();
}
#endregion
}
}
-241
View File
@@ -1,241 +0,0 @@
using ConvertSRTto3DASS;
using System;
using System.IO;
using System.Windows.Forms;
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;
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())
{
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 = GetSelectedModeValue();
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 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);
Converter.Main(new[]
{
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.");
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 = 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)
{
if (txtStatus.TextLength > 0)
txtStatus.AppendText(Environment.NewLine);
txtStatus.AppendText(message);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
-22
View File
@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConvertSRTto3DASSGUI
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
@@ -1,33 +0,0 @@
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")]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 imrayya Copyright (c) 2021-2026 imrayya
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+119 -26
View File
@@ -1,33 +1,126 @@
# SRT subtitles to 3D subtitles # SRT to 3D Subtitles
## The problem
I wanted watch a movie with subtitle, like I always do, but the subtitles for the movie I have was traditional .srt while the movie I was watching was a 3D movie which was **H**alf **S**ide **B**y **S**ide (HSBS). The subtitles in question were centered which was very disorienting to read while watching the movie. The solution? Write my own program to convert it for me.
## Convert Convert standard `.srt` subtitles into `.ass` format, offset so they display correctly in 3D video playback.
Currently just a command line program to convert a .srt subtitle file to an .ass subtitle file while making it compatible with the HSBS format. Either drag the SRT file on the .exe to convert the .srt file or run the command `SRTto3D.exe [insert your file name here]` and the program will convert it. Word of warning, the program will strip off any positional data off the srt. It will also likely fail with .srt file that contain a positional information.
## The Problem
When watching 3D movies (Side-by-Side, Over-Under, Anaglyph), centered subtitles get split between the left and right eye views, making them disorienting to read. This tool converts standard `.srt` subtitles into a 3D-compatible `.ass` format that keeps each eye's subtitles in the correct position.
## Features ## Features
_Basically nothing right now_
- [x] Convert a .srt to an .ass/.ssa
- [x] For HSBS 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
- [ ] Convert positional data of .srt subtitles
- [ ] Add changes which allows custom:
- [ ] Font
- [ ] Color
- [ ] Position
- [X] Margins
- [ ] Encoding
- [X] Add a proper GUI
- [ ] Any form of error handling or unit testing
- **SBS (Side-by-Side)** — subtitles split left/right for each eye
- **OU (Over-Under)** — subtitles split top/bottom for each eye
- **Anaglyph (Red/Green)** — subtitles offset with red/green color channels
- **CLI** — full command-line interface with configurable options
- **GUI** — lightweight Windows interface for easier mode selection
- **Formatting support** — converts HTML tags (`<b>`, `<i>`, `<u>`, `<font color>`) to ASS format codes
- **Resolution scaling** — adjusts subtitle positioning based on target resolution
## Usage
The application runs in two modes — **GUI** (no arguments) and **CLI** (with arguments).
### GUI
1. Launch `ConvertSRTto3DASS.exe` (or the Linux/macOS binary)
2. Select your `.srt` input file
3. Choose your 3D mode (SBS, OU, or Anaglyph)
4. Adjust settings as needed
5. Click **Convert**
> **Note:** The GUI is experimental. Feedback and bug reports are welcome!
### Command Line
The CLI runs on **Windows, Linux, and macOS**.
#### Drag and drop:
1. Drag and drop an `.srt` file onto `ConvertSRTto3DASS`
- Uses default settings (SBS mode, 1280x720)
2. The resulting `.ass` file will be saved in the same directory as the `.srt`
#### Command line:
```
ConvertSRTto3DASS.exe input.srt [options]
```
##### Available options:
```
--mode sbs|ou|rg 3D mode (default: sbs)
--resx <number> Output width
--resy <number> Output height
--baseresx <number> Scaling reference width (default: 1280)
--baseresy <number> Scaling reference height (default: 720)
--fontsize <number> Font size (default: 16)
--offsetx <number> RG eye separation
--bottomoffset <number> RG bottom offset
--sbssidemargin <number> SBS side margin
--outopmargin <number> OU top subtitle margin
--verticalmargin <number> General vertical margin
--output <path> Output file path
--help, -h, /? Show help
```
##### Examples:
```
ConvertSRTto3DASS.exe movie.srt --mode sbs
ConvertSRTto3DASS.exe movie.srt --mode ou --resx 1920 --resy 1080
ConvertSRTto3DASS.exe movie.srt --mode rg --offsetx 6 --bottomoffset 24
```
## Known Issues ## Known Issues
For some reason that I have yet to understand, Plex Transcoder doesn't not like the converted subtitles (it has a weird issue) but it works perfectly in VLC
## Warning - Subtitle positional data from `.srt` files is stripped during conversion.
The code is a complete and utter mess with very little comments in it. This was meant to be a private project and I haven't had the chance to clean it up yet.
## Requirements
- **.NET 8 SDK** — required to build
- **Windows** — the GUI runs on Windows
- **Linux/macOS** — the CLI runs cross-platform
## Building
```bash
# Build all projects
dotnet build
# Publish CLI (self-contained)
## Linux x64
dotnet publish ConvertSRTto3DASS.Cli -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
## OSX x64
dotnet publish ConvertSRTto3DASS.Cli -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
## OSX arm64
dotnet publish ConvertSRTto3DASS.Cli -c Release -r osx-arm64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
## Windows x64
dotnet publish ConvertSRTto3DASS.Cli -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
# Publish GUI (Windows x64 only, self-contained)
dotnet publish ConvertSRTto3DASS.Gui -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true
```
_Each publish produces a single .exe with no .dll sidecars._
## License
MIT License — Copyright © imrayya 2021-2026
## Contributing
Contributions are welcome. Please open an issue or submit a pull request.
### Contributors
- [imrayya](https://github.com/imrayya) — project owner
- [jae0815](https://github.com/jae0815)
- [fabio-noga](https://github.com/fabio-noga)
- [kaiju466](https://github.com/kaiju466)
## Disclaimer
This is a personal hobby project. The codebase is functional but not production-grade — minimal comments, no tests, and some rough edges. Use at your own discretion.