Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/split-dialogs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ on:
branches: [main]
paths:
- 'se5/SplitDialogs/**'
- 'se5/SplitDialogs.Tests/**'
- 'se5/Plugin-Shared/**'
- '.github/workflows/split-dialogs.yml'
pull_request:
paths:
- 'se5/SplitDialogs/**'
- 'se5/SplitDialogs.Tests/**'
- 'se5/Plugin-Shared/**'
- '.github/workflows/split-dialogs.yml'
workflow_dispatch:
Expand Down Expand Up @@ -90,8 +92,22 @@ jobs:
echo "ref=$ref" >> "$GITHUB_OUTPUT"
echo "release=$release_flag" >> "$GITHUB_OUTPUT"

test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Test
run: dotnet test se5/SplitDialogs.Tests/SplitDialogs.Tests.csproj -c Release

build:
needs: prepare
needs: [prepare, test]
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
108 changes: 108 additions & 0 deletions se5/SplitDialogs.Tests/BatchModeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using SubtitleEdit.Plugins.Shared;
using SubtitleEdit.Plugins.SplitDialogs;
using System.Text.Json;

namespace SplitDialogs.Tests;

/// <summary>Subtitle Edit Batch convert: "batch" splits every dialog headless, "configure" returns the options only.</summary>
public class BatchModeTests
{
private const string DialogSrt =
"1\n00:00:01,000 --> 00:00:04,000\n- Andro!\n- Ben, I've been oozed.\n\n" +
"2\n00:00:05,000 --> 00:00:07,000\nPlain line.\n\n" +
"3\n00:00:08,000 --> 00:00:10,000\n- How are you\n- Fine.\n";

private static PluginRequest Request(string requestType, string srt, string? settingsJson = null) => new()
{
RequestType = requestType,
Subtitle = new PluginSubtitle { Format = "SubRip", SubRip = srt, Native = srt },
Settings = settingsJson == null ? null : JsonDocument.Parse(settingsJson).RootElement.Clone(),
SettingsVersion = settingsJson == null ? null : 1,
};

private static List<string> Texts(PluginResponse response) =>
new SrtCueDocument(response.Subtitle!.Native).Cues.Select(c => string.Join("\n", c.Lines)).ToList();

[Fact]
public void Batch_SplitsAllDialogs_WithDefaultSettings()
{
var response = BatchMode.BuildBatchResponse(Request(PluginRequestType.Batch, DialogSrt));

Assert.Equal(PluginStatus.Ok, response.Status);
Assert.Equal("SubRip", response.Subtitle!.Format);
// Line 3 is not split by default: "How are you" does not end a sentence.
Assert.Equal(new[] { "Andro!", "Ben, I've been oozed.", "Plain line.", "- How are you\n- Fine." }, Texts(response));
Assert.Null(response.Settings);
Assert.Null(response.SettingsVersion);
}

[Fact]
public void Batch_UsesSavedSettings()
{
var response = BatchMode.BuildBatchResponse(
Request(PluginRequestType.Batch, DialogSrt, "{\"requireSentenceEnding\":false,\"gapMs\":100}"));

var cues = new SrtCueDocument(response.Subtitle!.Native).Cues;
Assert.Equal(new[] { "Andro!", "Ben, I've been oozed.", "Plain line.", "How are you", "Fine." },
cues.Select(c => string.Join("\n", c.Lines)));
Assert.Equal(100, cues[4].StartMs - cues[3].EndMs);
Assert.Equal(1000, cues[0].StartMs);
Assert.Equal(10000, cues[4].EndMs);
}

[Fact]
public void Batch_NoDialogs_ReturnsOkWithoutSubtitle()
{
var response = BatchMode.BuildBatchResponse(
Request(PluginRequestType.Batch, "1\n00:00:01,000 --> 00:00:02,000\nHello.\n"));

Assert.Equal(PluginStatus.Ok, response.Status);
Assert.Null(response.Subtitle);
}

[Fact]
public void Batch_Assa_KeepsNativeFormat()
{
const string assa =
"[Script Info]\nScriptType: v4.00+\n\n[Events]\n" +
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" +
"Dialogue: 0,0:00:01.00,0:00:04.00,Top,Bob,0,0,0,,- Hi!\\N- Hello.\n";
var request = new PluginRequest
{
RequestType = PluginRequestType.Batch,
Subtitle = new PluginSubtitle { Format = "Advanced Sub Station Alpha", Native = assa },
};

var response = BatchMode.BuildBatchResponse(request);

Assert.Equal("Advanced Sub Station Alpha", response.Subtitle!.Format);
var dialogues = response.Subtitle.Native.Split('\n').Where(l => l.StartsWith("Dialogue:")).ToList();
Assert.Equal(2, dialogues.Count);
Assert.All(dialogues, d => Assert.Contains(",Top,Bob,", d));
Assert.EndsWith(",Hi!", dialogues[0]);
Assert.EndsWith(",Hello.", dialogues[1]);
}

[Fact]
public void Configure_ReturnsSettingsWithoutSubtitle()
{
var response = BatchMode.BuildConfigureResponse(new SplitSettings(false, 80));

Assert.Equal(PluginStatus.Ok, response.Status);
Assert.Null(response.Subtitle);
Assert.Equal(1, response.SettingsVersion);
Assert.False(response.Settings!.Value.GetProperty("requireSentenceEnding").GetBoolean());
Assert.Equal(80, response.Settings.Value.GetProperty("gapMs").GetInt32());
}

[Fact]
public void Settings_RoundTrip_AndDefaults()
{
var saved = new SplitSettings(false, 42);
var request = new PluginRequest { Settings = saved.ToJson(), SettingsVersion = SplitSettings.Version };

Assert.Equal(saved, SplitSettings.Load(request));
Assert.Equal(SplitSettings.Default, SplitSettings.Load(new PluginRequest()));
Assert.Equal(SplitSettings.Default, SplitSettings.Load(new PluginRequest { Settings = saved.ToJson(), SettingsVersion = 99 }));
}
}
27 changes: 27 additions & 0 deletions se5/SplitDialogs.Tests/SplitDialogs.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<PropertyGroup>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SplitDialogs\SplitDialogs.csproj" />
</ItemGroup>

</Project>
54 changes: 54 additions & 0 deletions se5/SplitDialogs/BatchMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using SubtitleEdit.Plugins.Shared;

namespace SubtitleEdit.Plugins.SplitDialogs;

/// <summary>Subtitle Edit's Batch convert: "batch" splits headless, "configure" returns the settings only.</summary>
public static class BatchMode
{
/// <summary>
/// A "batch" request: split every dialog line in the file with the saved options, without
/// UI - what the window would do with its default "all rows checked". No dialogs is "ok"
/// without a subtitle.
/// </summary>
public static PluginResponse BuildBatchResponse(PluginRequest request)
{
var settings = SplitSettings.Load(request);
var document = CueDocument.Create(request.Subtitle);
var splits = new Dictionary<int, IReadOnlyList<SplitPart>>();
foreach (var cue in document.Cues)
{
if (!cue.CanSplit)
{
continue;
}

var texts = DialogSplitter.Split(cue.Lines, settings.RequireSentenceEnding);
if (texts != null)
{
splits[cue.Index] = SplitTiming.Distribute(cue.StartMs, cue.EndMs, texts, settings.GapMs);
}
}

if (splits.Count == 0)
{
return new PluginResponse { Status = PluginStatus.Ok, Message = "Split dialogs: no dialog lines found." };
}

var lineCount = splits.Values.Sum(p => p.Count);
return new PluginResponse
{
Status = PluginStatus.Ok,
Message = $"Split {splits.Count} dialog line(s) into {lineCount} lines.",
UndoDescription = "Split dialogs",
Subtitle = new PluginSubtitle
{
Format = document.FormatName,
Native = document.Build(splits),
},
};
}

/// <summary>A "configure" request: hand back the settings only, for the next Batch convert run.</summary>
public static PluginResponse BuildConfigureResponse(SplitSettings settings) =>
new() { Status = PluginStatus.Ok, Settings = settings.ToJson(), SettingsVersion = SplitSettings.Version };
}
21 changes: 20 additions & 1 deletion se5/SplitDialogs/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,33 @@
</StackPanel>
</WrapPanel>

<StackPanel Orientation="Horizontal"
<StackPanel x:Name="SelectButtonsPanel"
Orientation="Horizontal"
DockPanel.Dock="Top"
Spacing="10"
Margin="0,0,0,8">
<Button Content="Select all" Click="OnSelectAll" />
<Button Content="Select none" Click="OnSelectNone" />
</StackPanel>

<!-- Configure (Batch convert) -->
<StackPanel DockPanel.Dock="Top"
x:Name="ConfigurePanel"
IsVisible="False"
HorizontalAlignment="Center"
Margin="0,40,0,0"
Spacing="10"
MaxWidth="520">
<TextBlock Text="Batch convert settings"
FontSize="16"
FontWeight="SemiBold"
HorizontalAlignment="Center" />
<TextBlock TextWrapping="Wrap"
TextAlignment="Center"
Opacity="0.7"
Text="Set the options above and click OK. Batch convert then splits every dialog line in each file without showing this window - the same as clicking Apply here with all lines checked." />
</StackPanel>

<TextBlock DockPanel.Dock="Top"
x:Name="NoDialogsLabel"
FontSize="14"
Expand Down
Loading
Loading