kara-templater 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. kara_templater-0.1.0/LICENSE +21 -0
  2. kara_templater-0.1.0/MANIFEST.in +7 -0
  3. kara_templater-0.1.0/PKG-INFO +182 -0
  4. kara_templater-0.1.0/README.md +170 -0
  5. kara_templater-0.1.0/THIRD_PARTY.md +24 -0
  6. kara_templater-0.1.0/examples/karaoke.ass +14 -0
  7. kara_templater-0.1.0/kara_templater/__init__.py +16 -0
  8. kara_templater-0.1.0/kara_templater/__main__.py +23 -0
  9. kara_templater-0.1.0/kara_templater/ass.py +258 -0
  10. kara_templater-0.1.0/kara_templater/engine.py +528 -0
  11. kara_templater-0.1.0/kara_templater/layout.py +235 -0
  12. kara_templater-0.1.0/kara_templater.egg-info/PKG-INFO +182 -0
  13. kara_templater-0.1.0/kara_templater.egg-info/SOURCES.txt +27 -0
  14. kara_templater-0.1.0/kara_templater.egg-info/dependency_links.txt +1 -0
  15. kara_templater-0.1.0/kara_templater.egg-info/entry_points.txt +2 -0
  16. kara_templater-0.1.0/kara_templater.egg-info/top_level.txt +1 -0
  17. kara_templater-0.1.0/native/meson.build +23 -0
  18. kara_templater-0.1.0/native/meson.options +1 -0
  19. kara_templater-0.1.0/native/metrics.cpp +137 -0
  20. kara_templater-0.1.0/pyproject.toml +33 -0
  21. kara_templater-0.1.0/setup.cfg +4 -0
  22. kara_templater-0.1.0/setup.py +94 -0
  23. kara_templater-0.1.0/tests/test_templater.py +380 -0
  24. kara_templater-0.1.0/tools/collect_licenses.py +12 -0
  25. kara_templater-0.1.0/vendor/001-libass-metrics-api.patch +709 -0
  26. kara_templater-0.1.0/vendor/002-libass-metrics-crashfix.patch +75 -0
  27. kara_templater-0.1.0/vendor/LICENSE.libass +15 -0
  28. kara_templater-0.1.0/vendor/libass-0.17.5.tar.gz +0 -0
  29. kara_templater-0.1.0/vendor/runtime-licenses/NOTICE.txt +11 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kara-templater contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include native/metrics.cpp native/meson.build native/meson.options
2
+ include tools/collect_licenses.py
3
+ recursive-include tests *.py
4
+ recursive-include vendor *.tar.gz *.patch LICENSE.*
5
+ recursive-include vendor/runtime-licenses *
6
+ include THIRD_PARTY.md docs/releasing.md
7
+ recursive-include examples *.ass
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: kara-templater
3
+ Version: 0.1.0
4
+ Summary: Python karaoke template engine with native libass text metrics
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ License-File: vendor/LICENSE.libass
10
+ License-File: vendor/runtime-licenses/NOTICE.txt
11
+ Dynamic: license-file
12
+
13
+ # kara-templater
14
+
15
+ An ASS karaoke template engine rewritten in Python and C++. It runs without Aegisub or Lua.
16
+
17
+ The package keeps the karaoke templater's ASS template format, `$variables`, modifiers, and helper functions. **Expressions inside `!…!` and `code` lines use Python.** Lua code in existing templates must be rewritten manually; there is no Lua compatibility interpreter.
18
+
19
+ ## Installation
20
+
21
+ Requires Python 3.12 or newer. Binary wheels target CPython 3.12–3.14 on Linux x86-64 (glibc 2.28+) and Windows 10+ x64. They include the native library dependencies; no compiler or MSYS2 installation is needed to use a wheel.
22
+
23
+ ```sh
24
+ python -m pip install kara-templater
25
+ ```
26
+
27
+ Install the fonts specified by your subtitle styles. Missing fonts use system font fallback, and different fonts can change the layout.
28
+
29
+ ### Build from source on Linux
30
+
31
+ Install a C/C++ toolchain and native dependencies.
32
+
33
+ Arch Linux:
34
+
35
+ ```sh
36
+ sudo pacman -S --needed base-devel python python-pip autoconf automake libtool nasm \
37
+ freetype2 harfbuzz fribidi fontconfig libpng
38
+ ```
39
+
40
+ Debian / Ubuntu, with Python 3.12+:
41
+
42
+ ```sh
43
+ sudo apt install build-essential python3-dev python3-venv autoconf automake \
44
+ libtool nasm pkg-config libfreetype-dev libharfbuzz-dev libfribidi-dev \
45
+ libfontconfig-dev libpng-dev
46
+ ```
47
+
48
+ From the project directory:
49
+
50
+ ```sh
51
+ python -m venv .venv
52
+ . .venv/bin/activate
53
+ python -m pip install .
54
+ ```
55
+
56
+ ### Build from source on Windows
57
+
58
+ Use 64-bit Python from [python.org](https://www.python.org/downloads/windows/) and [MSYS2](https://www.msys2.org/). In the MSYS2 **UCRT64** shell, install the native toolchain:
59
+
60
+ ```sh
61
+ pacman -Syu
62
+ pacman -S --needed patch mingw-w64-ucrt-x86_64-gcc \
63
+ mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-nasm \
64
+ mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-harfbuzz \
65
+ mingw-w64-ucrt-x86_64-fribidi
66
+ ```
67
+
68
+ Restart the shell and complete the update if MSYS2 requests it. Then, in **PowerShell** at the project directory, build and install a wheel. Adjust the MSYS2 path if it is not installed at `C:\msys64`.
69
+
70
+ ```powershell
71
+ $env:Path = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;$env:Path"
72
+ python -m venv .venv
73
+ .\.venv\Scripts\Activate.ps1
74
+ python -m pip install delvewheel
75
+ python tools/collect_licenses.py
76
+ python -m pip wheel . --no-deps --wheel-dir dist
77
+ python -m delvewheel repair --strip --no-mangle libharfbuzz-0.dll --wheel-dir wheelhouse (Get-ChildItem dist\*.whl).FullName
78
+ python -m pip install --no-index --find-links wheelhouse kara-templater
79
+ ```
80
+
81
+ The repaired wheel bundles the required DLLs so it can run without MSYS2 on the destination machine. Windows uses the system DirectWrite/GDI font provider.
82
+
83
+ ## Command line
84
+
85
+ ```sh
86
+ kara-templater input.ass output.ass
87
+ python -m kara_templater input.ass output.ass
88
+ ```
89
+
90
+ Try the included example from a source checkout:
91
+
92
+ ```sh
93
+ kara-templater examples/karaoke.ass output.ass
94
+ ```
95
+
96
+ To correct horizontal coordinates for the video's actual dimensions:
97
+
98
+ ```sh
99
+ kara-templater input.ass output.ass --video-size 1920 1080
100
+ ```
101
+
102
+ Templates are ASS `Comment` events. Put the template type and modifiers in the **Effect** field. For example:
103
+
104
+ - Effect: `template syl noblank`
105
+ - Text: `!retime("syl")!{\an5\pos($scenter,$smiddle)\fad(50,80)}`
106
+
107
+ For lyrics such as `{\k50}你{\k50}好`, this generates one positioned effect event per nonblank syllable.
108
+
109
+ The output preserves templates, turns processed lyrics into comments with Effect `karaoke`, and marks generated events with Effect `fx`. Running the engine again removes old `fx` events and regenerates them.
110
+
111
+ ## Python API
112
+
113
+ ```python
114
+ from kara_templater import Document, Style, apply_templates, text_extents
115
+
116
+ source = Document.load("input.ass")
117
+ result = apply_templates(source)
118
+ result.save("output.ass")
119
+
120
+ width, height, descent, external_leading = text_extents(
121
+ Style(fontname="Noto Sans CJK SC", fontsize=48), "你好"
122
+ )
123
+ ```
124
+
125
+ `apply_templates` returns a new document without modifying its input. `Document.parse(text)` and `result.dumps()` support in-memory use.
126
+
127
+ ## Templates
128
+
129
+ | Type | Behavior |
130
+ | --- | --- |
131
+ | `template syl`, or no explicit type | Generate effects per syllable, including the empty syllable at index 0; usually combined with `noblank` |
132
+ | `template char` / `template syl char` | Generate effects per Unicode code point, retaining syllable timing |
133
+ | `template line` | Concatenate the per-syllable template into one complete effect event |
134
+ | `template pre-line` | Prefix a complete event; use the same identifier to merge it with a `line` template |
135
+ | `template furi` | Generate effects for furigana |
136
+ | `code once / line / syl / furi` | Execute Python statements in that scope; defaults to `once` |
137
+
138
+ Modifiers include `all`, `repeat N` / `loop N`, `notext`, `keeptags`, `noblank`, `multi`, `fx NAME`, and `fxgroup NAME`. Templates match their own style by default; `all` matches every style. Unknown modifiers, variables, and execution errors report the template event number instead of being silently ignored.
139
+
140
+ - `\k`, `\K`, `\kf`, `\ko`: syllable duration in centiseconds.
141
+ - `\-NAME`: inline effect name, inherited by subsequent syllables.
142
+ - `#` / `#`: continue the preceding syllable; `multi` runs the template separately for each highlight.
143
+ - `漢|かん` / `漢|かん`: separate base text from furigana. A furigana prefix `!` starts a new group; `<` allows spillback to the left. Full-width prefixes are also accepted. A missing furigana style is created as `STYLE-furigana` at half the base font size.
144
+
145
+ ### Variables and execution environment
146
+
147
+ `$variables` are case-insensitive:
148
+
149
+ - Line timing and indices: `$lstart`, `$lend`, `$ldur`, `$lmid`, `$li`, `$syln`.
150
+ - Syllable timing and indices: `$sstart`, `$send`, `$sdur`, `$skdur`, `$smid`, `$si`.
151
+ - Layout: prefix `l` or `s` to `left`, `center`, `right`, `width`, `top`, `middle`, `bottom`, `height`, `x`, or `y`.
152
+ - Current-scope aliases: `$start`, `$end`, `$dur`, `$kdur`, `$mid`, `$i`, and unprefixed layout variables.
153
+ - Other values: `$layer`, `$style`, `$actor`, `$margin_l`, `$margin_r`, `$margin_v`, `$margin_t`, `$margin_b`.
154
+
155
+ Expressions and code share Python variables. The environment provides `math`, `random`, `meta`, `styles`, `orgline`, `line`, `syl`, `basesyl`, `j`, `maxj`, `fxgroup`, and `text_extents`. `line` is the current mutable output event; in a code template it is the source lyric event. `syl` is a dictionary with attribute access.
156
+
157
+ Example Effect and Text pairs:
158
+
159
+ ```text
160
+ code once → amplitude = 12
161
+ code line → fxgroup["spark"] = orgline.actor == "solo"
162
+ template syl repeat 3 → !retime("syl")!{\an5\pos(!$x + amplitude * j!,$y)}
163
+ ```
164
+
165
+ An expression returning `None` produces an empty string. Integral floating-point values are written without a trailing `.0`.
166
+
167
+ ### Helpers
168
+
169
+ - `retime(mode, addstart=0, addend=0)`: modes are `syl`, `presyl`, `postsyl`, `line`, `preline`, `postline`, `start2syl`, `syl2end`, `sylpct`, and `set` / `abs`. Offsets are milliseconds, except that `sylpct` uses percentages of syllable duration. Timing is based on the current `line`, so consecutive calls use the previously modified times.
170
+ - `relayer(layer)`, `restyle(style)`: modify the current output event.
171
+ - `maxloop(count)` / `maxloops(count)`, `loopctl(j, count)`: control template loops dynamically.
172
+ - `remember(name, value, decorator=None)`, `recall(name, default=None)`, `remember_if(name, value, condition, decorator=None)`: store and retrieve values. The optional decorator is a Python function mapping a name to a memory key.
173
+ - `remember_line`, `remember_syl`, `remember_basesyl`: isolate remembered values by source event, current syllable, or base syllable.
174
+
175
+ ## Limitations and safety
176
+
177
+ - **Only process trusted subtitles.** Python expressions and code have the permissions of the current process. This is not a sandbox. Template iteration and output limits cannot stop arbitrary Python code from looping forever or performing malicious operations.
178
+ - The defaults are 10,000 iterations per template loop and 100,000 output events. The Python API accepts `max_iterations` and `max_output_lines` to change these limits.
179
+ - Only ASS v4.00+ is supported, not legacy SSA styles. Extra fields, unknown sections, and attachments are preserved, but byte-for-byte formatting is not guaranteed.
180
+ - Layout targets single-line karaoke using the base style. It does not simulate automatic wrapping, event collisions, inline style overrides, `\pos` / `\move`, or drawing geometry. `text_extents` supports `\N` hard breaks; syllable layout is not multiline-aware.
181
+ - `text_extents` returns logical advance width, font height, and descent, not visible glyph bounds. It preserves edge spaces and measures braces literally. External leading is always `0`. Pixel-identical results across fonts, platforms, or Aegisub's native font interfaces are not guaranteed.
182
+ - No GUI, video/audio processing, Automation plugin interface, or other Aegisub features are included.
@@ -0,0 +1,170 @@
1
+ # kara-templater
2
+
3
+ An ASS karaoke template engine rewritten in Python and C++. It runs without Aegisub or Lua.
4
+
5
+ The package keeps the karaoke templater's ASS template format, `$variables`, modifiers, and helper functions. **Expressions inside `!…!` and `code` lines use Python.** Lua code in existing templates must be rewritten manually; there is no Lua compatibility interpreter.
6
+
7
+ ## Installation
8
+
9
+ Requires Python 3.12 or newer. Binary wheels target CPython 3.12–3.14 on Linux x86-64 (glibc 2.28+) and Windows 10+ x64. They include the native library dependencies; no compiler or MSYS2 installation is needed to use a wheel.
10
+
11
+ ```sh
12
+ python -m pip install kara-templater
13
+ ```
14
+
15
+ Install the fonts specified by your subtitle styles. Missing fonts use system font fallback, and different fonts can change the layout.
16
+
17
+ ### Build from source on Linux
18
+
19
+ Install a C/C++ toolchain and native dependencies.
20
+
21
+ Arch Linux:
22
+
23
+ ```sh
24
+ sudo pacman -S --needed base-devel python python-pip autoconf automake libtool nasm \
25
+ freetype2 harfbuzz fribidi fontconfig libpng
26
+ ```
27
+
28
+ Debian / Ubuntu, with Python 3.12+:
29
+
30
+ ```sh
31
+ sudo apt install build-essential python3-dev python3-venv autoconf automake \
32
+ libtool nasm pkg-config libfreetype-dev libharfbuzz-dev libfribidi-dev \
33
+ libfontconfig-dev libpng-dev
34
+ ```
35
+
36
+ From the project directory:
37
+
38
+ ```sh
39
+ python -m venv .venv
40
+ . .venv/bin/activate
41
+ python -m pip install .
42
+ ```
43
+
44
+ ### Build from source on Windows
45
+
46
+ Use 64-bit Python from [python.org](https://www.python.org/downloads/windows/) and [MSYS2](https://www.msys2.org/). In the MSYS2 **UCRT64** shell, install the native toolchain:
47
+
48
+ ```sh
49
+ pacman -Syu
50
+ pacman -S --needed patch mingw-w64-ucrt-x86_64-gcc \
51
+ mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-nasm \
52
+ mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-harfbuzz \
53
+ mingw-w64-ucrt-x86_64-fribidi
54
+ ```
55
+
56
+ Restart the shell and complete the update if MSYS2 requests it. Then, in **PowerShell** at the project directory, build and install a wheel. Adjust the MSYS2 path if it is not installed at `C:\msys64`.
57
+
58
+ ```powershell
59
+ $env:Path = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;$env:Path"
60
+ python -m venv .venv
61
+ .\.venv\Scripts\Activate.ps1
62
+ python -m pip install delvewheel
63
+ python tools/collect_licenses.py
64
+ python -m pip wheel . --no-deps --wheel-dir dist
65
+ python -m delvewheel repair --strip --no-mangle libharfbuzz-0.dll --wheel-dir wheelhouse (Get-ChildItem dist\*.whl).FullName
66
+ python -m pip install --no-index --find-links wheelhouse kara-templater
67
+ ```
68
+
69
+ The repaired wheel bundles the required DLLs so it can run without MSYS2 on the destination machine. Windows uses the system DirectWrite/GDI font provider.
70
+
71
+ ## Command line
72
+
73
+ ```sh
74
+ kara-templater input.ass output.ass
75
+ python -m kara_templater input.ass output.ass
76
+ ```
77
+
78
+ Try the included example from a source checkout:
79
+
80
+ ```sh
81
+ kara-templater examples/karaoke.ass output.ass
82
+ ```
83
+
84
+ To correct horizontal coordinates for the video's actual dimensions:
85
+
86
+ ```sh
87
+ kara-templater input.ass output.ass --video-size 1920 1080
88
+ ```
89
+
90
+ Templates are ASS `Comment` events. Put the template type and modifiers in the **Effect** field. For example:
91
+
92
+ - Effect: `template syl noblank`
93
+ - Text: `!retime("syl")!{\an5\pos($scenter,$smiddle)\fad(50,80)}`
94
+
95
+ For lyrics such as `{\k50}你{\k50}好`, this generates one positioned effect event per nonblank syllable.
96
+
97
+ The output preserves templates, turns processed lyrics into comments with Effect `karaoke`, and marks generated events with Effect `fx`. Running the engine again removes old `fx` events and regenerates them.
98
+
99
+ ## Python API
100
+
101
+ ```python
102
+ from kara_templater import Document, Style, apply_templates, text_extents
103
+
104
+ source = Document.load("input.ass")
105
+ result = apply_templates(source)
106
+ result.save("output.ass")
107
+
108
+ width, height, descent, external_leading = text_extents(
109
+ Style(fontname="Noto Sans CJK SC", fontsize=48), "你好"
110
+ )
111
+ ```
112
+
113
+ `apply_templates` returns a new document without modifying its input. `Document.parse(text)` and `result.dumps()` support in-memory use.
114
+
115
+ ## Templates
116
+
117
+ | Type | Behavior |
118
+ | --- | --- |
119
+ | `template syl`, or no explicit type | Generate effects per syllable, including the empty syllable at index 0; usually combined with `noblank` |
120
+ | `template char` / `template syl char` | Generate effects per Unicode code point, retaining syllable timing |
121
+ | `template line` | Concatenate the per-syllable template into one complete effect event |
122
+ | `template pre-line` | Prefix a complete event; use the same identifier to merge it with a `line` template |
123
+ | `template furi` | Generate effects for furigana |
124
+ | `code once / line / syl / furi` | Execute Python statements in that scope; defaults to `once` |
125
+
126
+ Modifiers include `all`, `repeat N` / `loop N`, `notext`, `keeptags`, `noblank`, `multi`, `fx NAME`, and `fxgroup NAME`. Templates match their own style by default; `all` matches every style. Unknown modifiers, variables, and execution errors report the template event number instead of being silently ignored.
127
+
128
+ - `\k`, `\K`, `\kf`, `\ko`: syllable duration in centiseconds.
129
+ - `\-NAME`: inline effect name, inherited by subsequent syllables.
130
+ - `#` / `#`: continue the preceding syllable; `multi` runs the template separately for each highlight.
131
+ - `漢|かん` / `漢|かん`: separate base text from furigana. A furigana prefix `!` starts a new group; `<` allows spillback to the left. Full-width prefixes are also accepted. A missing furigana style is created as `STYLE-furigana` at half the base font size.
132
+
133
+ ### Variables and execution environment
134
+
135
+ `$variables` are case-insensitive:
136
+
137
+ - Line timing and indices: `$lstart`, `$lend`, `$ldur`, `$lmid`, `$li`, `$syln`.
138
+ - Syllable timing and indices: `$sstart`, `$send`, `$sdur`, `$skdur`, `$smid`, `$si`.
139
+ - Layout: prefix `l` or `s` to `left`, `center`, `right`, `width`, `top`, `middle`, `bottom`, `height`, `x`, or `y`.
140
+ - Current-scope aliases: `$start`, `$end`, `$dur`, `$kdur`, `$mid`, `$i`, and unprefixed layout variables.
141
+ - Other values: `$layer`, `$style`, `$actor`, `$margin_l`, `$margin_r`, `$margin_v`, `$margin_t`, `$margin_b`.
142
+
143
+ Expressions and code share Python variables. The environment provides `math`, `random`, `meta`, `styles`, `orgline`, `line`, `syl`, `basesyl`, `j`, `maxj`, `fxgroup`, and `text_extents`. `line` is the current mutable output event; in a code template it is the source lyric event. `syl` is a dictionary with attribute access.
144
+
145
+ Example Effect and Text pairs:
146
+
147
+ ```text
148
+ code once → amplitude = 12
149
+ code line → fxgroup["spark"] = orgline.actor == "solo"
150
+ template syl repeat 3 → !retime("syl")!{\an5\pos(!$x + amplitude * j!,$y)}
151
+ ```
152
+
153
+ An expression returning `None` produces an empty string. Integral floating-point values are written without a trailing `.0`.
154
+
155
+ ### Helpers
156
+
157
+ - `retime(mode, addstart=0, addend=0)`: modes are `syl`, `presyl`, `postsyl`, `line`, `preline`, `postline`, `start2syl`, `syl2end`, `sylpct`, and `set` / `abs`. Offsets are milliseconds, except that `sylpct` uses percentages of syllable duration. Timing is based on the current `line`, so consecutive calls use the previously modified times.
158
+ - `relayer(layer)`, `restyle(style)`: modify the current output event.
159
+ - `maxloop(count)` / `maxloops(count)`, `loopctl(j, count)`: control template loops dynamically.
160
+ - `remember(name, value, decorator=None)`, `recall(name, default=None)`, `remember_if(name, value, condition, decorator=None)`: store and retrieve values. The optional decorator is a Python function mapping a name to a memory key.
161
+ - `remember_line`, `remember_syl`, `remember_basesyl`: isolate remembered values by source event, current syllable, or base syllable.
162
+
163
+ ## Limitations and safety
164
+
165
+ - **Only process trusted subtitles.** Python expressions and code have the permissions of the current process. This is not a sandbox. Template iteration and output limits cannot stop arbitrary Python code from looping forever or performing malicious operations.
166
+ - The defaults are 10,000 iterations per template loop and 100,000 output events. The Python API accepts `max_iterations` and `max_output_lines` to change these limits.
167
+ - Only ASS v4.00+ is supported, not legacy SSA styles. Extra fields, unknown sections, and attachments are preserved, but byte-for-byte formatting is not guaranteed.
168
+ - Layout targets single-line karaoke using the base style. It does not simulate automatic wrapping, event collisions, inline style overrides, `\pos` / `\move`, or drawing geometry. `text_extents` supports `\N` hard breaks; syllable layout is not multiline-aware.
169
+ - `text_extents` returns logical advance width, font height, and descent, not visible glyph bounds. It preserves edge spaces and measures braces literally. External leading is always `0`. Pixel-identical results across fonts, platforms, or Aegisub's native font interfaces are not guaranteed.
170
+ - No GUI, video/audio processing, Automation plugin interface, or other Aegisub features are included.
@@ -0,0 +1,24 @@
1
+ # Third-party sources
2
+
3
+ The Python template engine and C++ binding are newly written. The observable template interface was studied in Aegisub's `automation/autoload/kara-templater.lua`, `automation/include/karaskel-auto4.lua`, and karaoke parser. No Lua scripts or Lua runtime are included in this package.
4
+
5
+ Text measurement uses libass 0.17.5 under the ISC license. Its original source archive, license, and two metrics patches are included in `vendor/`:
6
+
7
+ - `libass-0.17.5.tar.gz`: https://github.com/libass/libass/releases/tag/0.17.5
8
+ - `001-libass-metrics-api.patch`: experimental metrics API associated with https://github.com/libass/libass/pull/856
9
+ - `002-libass-metrics-crashfix.patch`: metrics lifetime and empty-event corrections distributed with aegisub-karaskel-fix.
10
+ - `LICENSE.libass`: the upstream libass license.
11
+
12
+ The native build compiles the bundled patched libass statically; Linux hides its symbols. It does not replace or link against the system libass. FreeType, HarfBuzz, and FriBidi are shared dependencies. Linux also uses Fontconfig and libpng; Windows uses the system DirectWrite/GDI font provider instead of Fontconfig.
13
+
14
+ Release wheels bundle non-system shared dependencies using auditwheel or delvewheel. Their upstream licenses are included under `vendor/runtime-licenses/` in distribution license metadata; the project's MIT license does not replace these licenses. These include FriBidi's LGPL license and the GCC runtime licenses and exceptions when those libraries are bundled. Dependency source packages are available through the MSYS2 and distribution repositories listed in `vendor/runtime-licenses/NOTICE.txt`. Source installs use separately installed native dependencies. Building libass itself uses the bundled source without downloading it.
15
+
16
+ The C++ binding consumes run advances, ascent, and descent from `ass_get_metrics`. No bitmap-boundary scanning is used. Non-breaking spaces retain edge-space advances; hard breaks are measured as separate rows. The metrics API is experimental and pinned to the bundled version.
17
+
18
+ Build requirements are setuptools, Python headers, a C/C++ toolchain, pkg-config, patch, and the native dependencies listed above. Linux uses Autoconf, Automake, and libtool; Windows uses MSYS2 UCRT64, Meson, and Ninja. NASM enables libass's x86 assembly optimizations. The renderer stays inside the extension module and measurements hold the Python GIL. Build and publication configuration is documented in `docs/releasing.md`.
19
+
20
+ Run verification with:
21
+
22
+ ```sh
23
+ python -m unittest discover -s tests -v
24
+ ```
@@ -0,0 +1,14 @@
1
+ [Script Info]
2
+ Title: Python karaoke template
3
+ ScriptType: v4.00+
4
+ PlayResX: 1280
5
+ PlayResY: 720
6
+
7
+ [V4+ Styles]
8
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
9
+ Style: Default,Arial,48,&H00FFFFFF,&H0000FFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,30,30,40,1
10
+
11
+ [Events]
12
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
13
+ Comment: 1,0:00:00.00,0:00:00.00,Default,,0,0,0,template syl noblank,!retime("syl")!{\an5\pos($scenter,$smiddle)\fad(50,80)\fscx120\fscy120\t(0,$sdur,\fscx100\fscy100)}
14
+ Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,{\k50}你{\k50}好{\k50}世{\k50}界
@@ -0,0 +1,16 @@
1
+ from .ass import Document, Line, Record, Style
2
+ from .engine import Engine, TemplateError, apply_templates
3
+ from .layout import preprocess, split_karaoke, text_extents
4
+
5
+ __all__ = [
6
+ "Document",
7
+ "Engine",
8
+ "Line",
9
+ "Record",
10
+ "Style",
11
+ "TemplateError",
12
+ "apply_templates",
13
+ "preprocess",
14
+ "split_karaoke",
15
+ "text_extents",
16
+ ]
@@ -0,0 +1,23 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ from . import Document, apply_templates
5
+
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser(
9
+ description="Apply trusted Python karaoke templates to ASS subtitles"
10
+ )
11
+ parser.add_argument("input", type=Path)
12
+ parser.add_argument("output", type=Path)
13
+ parser.add_argument("--video-size", nargs=2, type=int, metavar=("WIDTH", "HEIGHT"))
14
+ args = parser.parse_args()
15
+ try:
16
+ result = apply_templates(Document.load(args.input), video_size=args.video_size)
17
+ result.save(args.output)
18
+ except (OSError, ValueError, RuntimeError) as error:
19
+ parser.exit(1, f"kara-templater: {error}\n")
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()