From 4e586c73e9f250e44ca10b3e5589cc3bebba8c85 Mon Sep 17 00:00:00 2001 From: niksedk Date: Sun, 17 May 2026 15:17:55 +0200 Subject: [PATCH] Add Typewriter effect SE5 plugin (Avalonia) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the SE4 "Effect → Typewriter…" feature (issue #10942) as a standalone SE5 plugin. Each selected subtitle line is exploded into several short lines that progressively reveal the text character by character, with an optional end delay holding the fully-typed line before the original end time. The plugin is an Avalonia 11 / .NET 8 app: - Settings dialog with a numeric input for the end delay. - Inherits Dark/Light theme from request.theme. - Persists endDelay via the plugin Settings round-trip. - Inline SRT parser/serializer; ASSA overrides and html i/b/u/font tags are preserved across the exploded paragraphs. Adds .github/workflows/typewriter.yml that mirrors haxor.yml: matrix self-contained publish for the six supported RIDs, plugin.json rewritten to per-OS executables block per zip, and (on dispatch with a tag) a GitHub release with all six zips attached. Updates se5-plugins.json to point at the planned se5-typewriter-v1.0 release assets via the per-platform downloads map. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/typewriter.yml | 107 +++++++++ se5-plugins.json | 17 ++ se5/TypewriterEffect/App.axaml | 8 + se5/TypewriterEffect/App.axaml.cs | 34 +++ se5/TypewriterEffect/MainWindow.axaml | 54 +++++ se5/TypewriterEffect/MainWindow.axaml.cs | 90 ++++++++ se5/TypewriterEffect/PluginContract.cs | 40 ++++ se5/TypewriterEffect/Program.cs | 63 ++++++ se5/TypewriterEffect/README.md | 37 ++++ se5/TypewriterEffect/SubRipParser.cs | 88 ++++++++ se5/TypewriterEffect/TypewriterEffect.csproj | 22 ++ se5/TypewriterEffect/TypewriterEngine.cs | 219 +++++++++++++++++++ se5/TypewriterEffect/app.manifest | 10 + se5/TypewriterEffect/plugin.json | 12 + 14 files changed, 801 insertions(+) create mode 100644 .github/workflows/typewriter.yml create mode 100644 se5/TypewriterEffect/App.axaml create mode 100644 se5/TypewriterEffect/App.axaml.cs create mode 100644 se5/TypewriterEffect/MainWindow.axaml create mode 100644 se5/TypewriterEffect/MainWindow.axaml.cs create mode 100644 se5/TypewriterEffect/PluginContract.cs create mode 100644 se5/TypewriterEffect/Program.cs create mode 100644 se5/TypewriterEffect/README.md create mode 100644 se5/TypewriterEffect/SubRipParser.cs create mode 100644 se5/TypewriterEffect/TypewriterEffect.csproj create mode 100644 se5/TypewriterEffect/TypewriterEngine.cs create mode 100644 se5/TypewriterEffect/app.manifest create mode 100644 se5/TypewriterEffect/plugin.json diff --git a/.github/workflows/typewriter.yml b/.github/workflows/typewriter.yml new file mode 100644 index 0000000..7aca494 --- /dev/null +++ b/.github/workflows/typewriter.yml @@ -0,0 +1,107 @@ +name: Build TypewriterEffect (SE5) + +on: + push: + branches: [main] + paths: + - 'se5/TypewriterEffect/**' + - '.github/workflows/typewriter.yml' + pull_request: + paths: + - 'se5/TypewriterEffect/**' + - '.github/workflows/typewriter.yml' + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish the zips under (e.g. se5-typewriter-v1.0). Leave empty to build artifacts only.' + required: false + type: string + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - rid: win-x64 + os: windows + exe: TypewriterEffect.exe + - rid: win-arm64 + os: windows + exe: TypewriterEffect.exe + - rid: linux-x64 + os: linux + exe: TypewriterEffect + - rid: linux-arm64 + os: linux + exe: TypewriterEffect + - rid: osx-x64 + os: macos + exe: TypewriterEffect + - rid: osx-arm64 + os: macos + exe: TypewriterEffect + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Publish self-contained (${{ matrix.rid }}) + working-directory: se5/TypewriterEffect + run: | + dotnet publish TypewriterEffect.csproj \ + -c Release \ + -r ${{ matrix.rid }} \ + --self-contained true \ + -p:PublishSingleFile=true \ + -p:IncludeNativeLibrariesForSelfExtract=true \ + -o staging/TypewriterEffect + + - name: Rewrite plugin.json for ${{ matrix.os }} + working-directory: se5/TypewriterEffect + run: | + jq ' + del(.runtime, .entry) | + .executables = { "${{ matrix.os }}": "${{ matrix.exe }}" } + ' plugin.json > staging/TypewriterEffect/plugin.json + + - name: Package zip + working-directory: se5/TypewriterEffect/staging + run: zip -r "$GITHUB_WORKSPACE/TypewriterEffect-${{ matrix.rid }}.zip" TypewriterEffect + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: TypewriterEffect-${{ matrix.rid }} + path: TypewriterEffect-${{ matrix.rid }}.zip + + release: + name: Publish GitHub Release + needs: build + if: github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all zips + uses: actions/download-artifact@v4 + with: + path: dist + pattern: TypewriterEffect-* + merge-multiple: true + + - name: Create release and upload zips + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "${{ github.event.inputs.tag }}" dist/TypewriterEffect-*.zip \ + --repo "${{ github.repository }}" \ + --target "${{ github.sha }}" \ + --title "TypewriterEffect ${{ github.event.inputs.tag }}" \ + --notes "Self-contained TypewriterEffect plugin builds for win/linux/osx (x64 + arm64)." diff --git a/se5-plugins.json b/se5-plugins.json index ae76ba2..add5a84 100644 --- a/se5-plugins.json +++ b/se5-plugins.json @@ -1,5 +1,22 @@ { "plugins": [ + { + "name": "Typewriter effect", + "description": "Splits each subtitle line into short timed parts that progressively reveal the text, character by character.", + "version": "1.0.0", + "author": "Subtitle Edit", + "url": "https://github.com/SubtitleEdit/plugins/tree/main/se5/TypewriterEffect", + "date": "2026-05-17", + "minSeVersion": "5.0.0", + "downloads": { + "win-x64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-win-x64.zip", + "win-arm64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-win-arm64.zip", + "linux-x64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-linux-x64.zip", + "linux-arm64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-linux-arm64.zip", + "osx-x64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-osx-x64.zip", + "osx-arm64": "https://github.com/SubtitleEdit/plugins/releases/download/se5-typewriter-v1.0/TypewriterEffect-osx-arm64.zip" + } + }, { "name": "Haxor", "description": "Translates the text of the selected lines (or all lines) to haxor.", diff --git a/se5/TypewriterEffect/App.axaml b/se5/TypewriterEffect/App.axaml new file mode 100644 index 0000000..c802d73 --- /dev/null +++ b/se5/TypewriterEffect/App.axaml @@ -0,0 +1,8 @@ + + + + + diff --git a/se5/TypewriterEffect/App.axaml.cs b/se5/TypewriterEffect/App.axaml.cs new file mode 100644 index 0000000..6e1699b --- /dev/null +++ b/se5/TypewriterEffect/App.axaml.cs @@ -0,0 +1,34 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +namespace SubtitleEdit.Plugins.TypewriterEffect; + +public partial class App : Application +{ + /// Set by Program.Main before Avalonia starts. + public static PluginRequest? PendingRequest; + + /// Filled in by MainWindow when the user clicks OK or Cancel. + public static PluginResponse? Response; + + public override void Initialize() => AvaloniaXamlLoader.Load(this); + + public override void OnFrameworkInitializationCompleted() + { + if (PendingRequest is not null) + { + RequestedThemeVariant = string.Equals(PendingRequest.Theme, "Dark", System.StringComparison.OrdinalIgnoreCase) + ? ThemeVariant.Dark + : ThemeVariant.Light; + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(PendingRequest); + } + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/se5/TypewriterEffect/MainWindow.axaml b/se5/TypewriterEffect/MainWindow.axaml new file mode 100644 index 0000000..bfc130c --- /dev/null +++ b/se5/TypewriterEffect/MainWindow.axaml @@ -0,0 +1,54 @@ + + + +