mirror of
https://github.com/imrayya/SRTto3Dsubtitles.git
synced 2026-09-21 10:17:27 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40ce11d068 | |||
| 743900f2f7 |
@@ -0,0 +1,151 @@
|
||||
using System.Text;
|
||||
|
||||
namespace ConvertSRTto3DASS;
|
||||
|
||||
internal static class Cli
|
||||
{
|
||||
public static void Run(string[] args)
|
||||
{
|
||||
if (args.Length == 0 || args[0] is "--help" or "-h" or "/?")
|
||||
{
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
var inputPath = args[0];
|
||||
if (!File.Exists(inputPath))
|
||||
{
|
||||
Console.Error.WriteLine($"Input file not found: {inputPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.Error.WriteLine(ex.ToString());
|
||||
}
|
||||
|
||||
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,22 @@
|
||||
<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>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ConvertSRTto3DASS.Core\ConvertSRTto3DASS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace ConvertSRTto3DASS;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
static void Main(string[] args) => Cli.Run(args);
|
||||
}
|
||||
@@ -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,16 @@
|
||||
<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>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,273 @@
|
||||
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)
|
||||
{
|
||||
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>
|
||||
Generated
+197
@@ -0,0 +1,197 @@
|
||||
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), ReadOnly = true };
|
||||
button_BrowseInput = new Button { Text = "Browse...", Location = new Point(890, 20), Size = new Size(90, 25), Font = segoe, FlatStyle = FlatStyle.Flat };
|
||||
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), ReadOnly = true };
|
||||
button_BrowseOutput = new Button { Text = "Browse...", Location = new Point(890, 50), Size = new Size(90, 25), Font = segoe, FlatStyle = FlatStyle.Flat };
|
||||
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)"]);
|
||||
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 }
|
||||
};
|
||||
|
||||
// ---- 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace ConvertSRTto3DASS;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ConvertSRTto3DASSGUI.Properties
|
||||
namespace ConvertSRTto3DASS.Properties
|
||||
{
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ConvertSRTto3DASSGUI.Properties
|
||||
namespace ConvertSRTto3DASS.Properties
|
||||
{
|
||||
|
||||
|
||||
+18
-18
@@ -1,11 +1,13 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35208.52
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
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
|
||||
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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -13,19 +15,17 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7D7925DE-ADBD-4A26-B12B-8F12B68D3BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B5E10CFB-7432-4C4B-80FB-1B9349811932}
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
Generated
-551
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 imrayya
|
||||
Copyright (c) 2021-2026 imrayya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,33 +1,112 @@
|
||||
# SRT subtitles 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.
|
||||
# SRT to 3D Subtitles
|
||||
|
||||
## Convert
|
||||
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.
|
||||
Convert standard `.srt` subtitles into `.ass` format, offset so they display correctly in 3D video playback.
|
||||
|
||||
## 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
|
||||
_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**
|
||||
|
||||
### Command Line
|
||||
|
||||
Pass any argument to trigger CLI mode:
|
||||
|
||||
```
|
||||
ConvertSRTto3DASS.exe input.srt [options]
|
||||
```
|
||||
|
||||
Or drag and drop an `.srt` file onto the executable.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
> **Note:** The GUI is experimental. Feedback and bug reports are welcome!
|
||||
|
||||
## 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
|
||||
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.
|
||||
- Plex Transcoder has issues with the converted subtitles. VLC works correctly.
|
||||
- Subtitle positional data from `.srt` files is stripped during conversion.
|
||||
|
||||
## 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 (cross-platform)
|
||||
dotnet publish ConvertSRTto3DASS.Cli -c Release -r linux-x64 --self-contained false -p:PublishSingleFile=true
|
||||
dotnet publish ConvertSRTto3DASS.Cli -c Release -r osx-x64 --self-contained false -p:PublishSingleFile=true
|
||||
dotnet publish ConvertSRTto3DASS.Cli -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true
|
||||
|
||||
# Publish GUI (Windows only)
|
||||
dotnet publish ConvertSRTto3DASS.Gui -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
Reference in New Issue
Block a user