akarisub 0.2.2 → 1.0.0

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 (76) hide show
  1. package/README.md +198 -167
  2. package/THIRD_PARTY_NOTICES.md +55 -163
  3. package/dist/COPYRIGHT +56 -164
  4. package/dist/index.d.ts +12 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +11 -147
  7. package/dist/index.js.map +1 -0
  8. package/dist/ts/{ts/akarisub.d.ts → akarisub.d.ts} +80 -99
  9. package/dist/ts/akarisub.d.ts.map +1 -0
  10. package/dist/ts/akarisub.js +2086 -0
  11. package/dist/ts/akarisub.js.map +1 -0
  12. package/dist/ts/timing.d.ts +63 -0
  13. package/dist/ts/timing.d.ts.map +1 -0
  14. package/dist/ts/timing.js +219 -0
  15. package/dist/ts/timing.js.map +1 -0
  16. package/dist/ts/{ts/types.d.ts → types.d.ts} +103 -78
  17. package/dist/ts/types.d.ts.map +1 -0
  18. package/dist/ts/types.js +2 -0
  19. package/dist/ts/{ts/types.js.map → types.js.map} +1 -1
  20. package/dist/ts/{ts/utils.d.ts → utils.d.ts} +0 -31
  21. package/dist/ts/utils.d.ts.map +1 -0
  22. package/dist/ts/{ts/utils.js → utils.js} +0 -56
  23. package/dist/ts/utils.js.map +1 -0
  24. package/dist/ts/wasm.d.ts +12 -0
  25. package/dist/ts/wasm.d.ts.map +1 -0
  26. package/dist/ts/wasm.js +46 -0
  27. package/dist/ts/wasm.js.map +1 -0
  28. package/dist/ts/{ts/webgl2-renderer.d.ts → webgl2-renderer.d.ts} +0 -12
  29. package/dist/ts/webgl2-renderer.d.ts.map +1 -0
  30. package/dist/ts/{ts/webgl2-renderer.js → webgl2-renderer.js} +0 -27
  31. package/dist/ts/webgl2-renderer.js.map +1 -0
  32. package/dist/ts/{ts/webgpu-renderer.d.ts → webgpu-renderer.d.ts} +17 -16
  33. package/dist/ts/webgpu-renderer.d.ts.map +1 -0
  34. package/dist/ts/{ts/webgpu-renderer.js → webgpu-renderer.js} +148 -71
  35. package/dist/ts/webgpu-renderer.js.map +1 -0
  36. package/dist/ts/worker.d.ts +2 -0
  37. package/dist/ts/{ts/worker.d.ts.map → worker.d.ts.map} +1 -1
  38. package/dist/ts/{ts/worker.js → worker.js} +1064 -264
  39. package/dist/ts/worker.js.map +1 -0
  40. package/dist/wrapper.d.ts +8 -0
  41. package/dist/wrapper.d.ts.map +1 -0
  42. package/dist/{ts/index.js → wrapper.js} +4 -14
  43. package/dist/wrapper.js.map +1 -0
  44. package/package.json +24 -22
  45. package/pkg/akarisub.js +53 -0
  46. package/{dist/akarisub-worker.wasm → pkg/akarisub.wasm} +0 -0
  47. package/src/wrapper.ts +46 -0
  48. package/dist/akarisub-worker.js +0 -39
  49. package/dist/akarisub.umd.js +0 -159
  50. package/dist/ts/index.d.ts +0 -14
  51. package/dist/ts/index.d.ts.map +0 -1
  52. package/dist/ts/index.js.map +0 -1
  53. package/dist/ts/ts/akarisub.d.ts.map +0 -1
  54. package/dist/ts/ts/akarisub.js +0 -1073
  55. package/dist/ts/ts/akarisub.js.map +0 -1
  56. package/dist/ts/ts/types.d.ts.map +0 -1
  57. package/dist/ts/ts/types.js +0 -5
  58. package/dist/ts/ts/utils.d.ts.map +0 -1
  59. package/dist/ts/ts/utils.js.map +0 -1
  60. package/dist/ts/ts/webgl2-renderer.d.ts.map +0 -1
  61. package/dist/ts/ts/webgl2-renderer.js.map +0 -1
  62. package/dist/ts/ts/webgpu-renderer.d.ts.map +0 -1
  63. package/dist/ts/ts/webgpu-renderer.js.map +0 -1
  64. package/dist/ts/ts/worker.d.ts +0 -6
  65. package/dist/ts/ts/worker.js.map +0 -1
  66. package/dist/ts/wrapper.d.ts +0 -8
  67. package/dist/ts/wrapper.d.ts.map +0 -1
  68. package/dist/ts/wrapper.js +0 -9
  69. package/dist/ts/wrapper.js.map +0 -1
  70. package/src/ts/akarisub.ts +0 -1272
  71. package/src/ts/types.ts +0 -397
  72. package/src/ts/utils.ts +0 -512
  73. package/src/ts/webgl2-renderer.ts +0 -426
  74. package/src/ts/webgpu-renderer.ts +0 -691
  75. package/src/ts/worker.ts +0 -2052
  76. /package/{dist → assets}/default.woff2 +0 -0
package/README.md CHANGED
@@ -49,10 +49,10 @@ deno add jsr:@altq/akarisub
49
49
 
50
50
  ## Usage
51
51
 
52
- By default all you need to do is copy the files from the `dist/` folder of the repository into the same folder as where your JS runs, then do:
52
+ In most bundler-based projects, no manual worker setup is required. AkariSub resolves the WASM glue and binary relative to the package module URL, so bundlers such as Vite, webpack, and Rollup can emit the assets automatically.
53
53
 
54
54
  ```js
55
- import AkariSub from './index.js'
55
+ import AkariSub from 'akarisub'
56
56
 
57
57
  const renderer = new AkariSub({
58
58
  video: document.querySelector('video'),
@@ -60,29 +60,21 @@ const renderer = new AkariSub({
60
60
  })
61
61
  ```
62
62
 
63
- `Note:` while the `dist/` folder includes a UMD dist it still uses modern syntax. If you want backwards compatibility with older browsers I recommend you run it tru babel.
64
-
65
- If you use a bundler like Vite, you can instead do:
63
+ If your app serves package files in a way that does not expose those emitted assets to the browser, you can still provide the public fallback by copying the WASM file and its JS glue to `/akarisub/`:
66
64
 
67
- ```js
68
- import AkariSub from 'akarisub'
69
- import workerUrl from 'akarisub/worker?url'
70
- import wasmUrl from 'akarisub/worker.wasm?url'
71
-
72
- const renderer = new AkariSub({
73
- video: document.querySelector('video'),
74
- subContent: subtitleString,
75
- workerUrl,
76
- wasmUrl
77
- })
65
+ ```bash
66
+ mkdir -p public/akarisub
67
+ cp node_modules/akarisub/pkg/akarisub.wasm node_modules/akarisub/pkg/akarisub.js public/akarisub/
78
68
  ```
79
69
 
70
+ The worker is created from the package module URL. `workerUrl`, `wasmUrl`, and `glueUrl` remain in the option type for overrides and do not need to be set in bundler-based apps.
71
+
80
72
  ## Using only with canvas
81
73
 
82
74
  You're also able to use it without any video. However, that requires you to set the time the subtitles should render at yourself. Disable `onDemandRender` (it relies on video frame callbacks) and drive the clock manually:
83
75
 
84
76
  ```js
85
- import AkariSub from './index.js'
77
+ import AkariSub from 'akarisub'
86
78
 
87
79
  const renderer = new AkariSub({
88
80
  canvas: document.querySelector('canvas'),
@@ -94,6 +86,36 @@ const renderer = new AkariSub({
94
86
  renderer.setCurrentTime(true, 15)
95
87
  ```
96
88
 
89
+ Custom canvases stay on the main thread by default. Set both `offscreenRender: true` and `rawAssImageGpu: true` to opt into worker WebGL2 raw-mask composition when dense overlays benefit from it.
90
+
91
+ ### Timing semantics
92
+
93
+ AkariSub sends libass an integer millisecond timestamp. Fractional media times are floored to the current millisecond, matching libass's `Start <= now < Start + Duration` event boundaries. `timeOffset` and `renderAhead` are measured in seconds.
94
+
95
+ By default, `adaptiveTiming` predicts the media time at which a completed canvas paint will become visible, compensating bounded rendering and delivery latency. Pause and seek frames always use the exact media timestamp. Set `adaptiveTiming: false` when deterministic frame sampling is more important than compensating live presentation latency.
96
+
97
+ For frame-locked VOD playback, provide the encoded video's presentation timestamps in seconds. AkariSub prepares a small window of full subtitle bitmaps and commits the matching bitmap inside `requestVideoFrameCallback`, which keeps fast `\t` and `\move` animation sampling aligned with libass:
98
+
99
+ ```js
100
+ const frameTimeline = new Float64Array([0, 0.041708, 0.083417])
101
+ const renderer = new AkariSub({
102
+ video,
103
+ subContent,
104
+ frameTimeline,
105
+ framePrefetch: 2
106
+ })
107
+
108
+ // Timelines can also arrive after renderer construction.
109
+ renderer.setFrameTimeline(frameTimeline)
110
+ renderer.setFrameTimeline(null) // return to adaptive continuous-time rendering
111
+ ```
112
+
113
+ Use timestamps from the encoded rendition the browser plays, normalized to the browser media clock. Passing a timeline remains optional; cache misses and unsupported paths automatically use normal adaptive rendering.
114
+
115
+ If the browser timeline is normalized from decode time rather than the first display PTS, preserve that initial B-frame reorder gap in the array and set `frameTimeline.subtitleTimeOffset` to the first frame timestamp. AkariSub uses the unmodified array to locate the visible video frame, then removes the offset only when sampling libass.
116
+
117
+ `subtitleTimeOffset` is signed. When the source video starts after the container subtitle clock, subtract that source lead from the reorder offset. For example, an encoded reorder gap of `0.083422` seconds and a source video start of `0.007` seconds use `subtitleTimeOffset = 0.076422`, so the first displayed frame is sampled by libass at `0.007` just like mpv.
118
+
97
119
  ## Changing subtitles
98
120
 
99
121
  You're not limited to only display the subtitle file you referenced in your options. You're able to dynamically change subtitles on the fly. There's four methods that you can use for this specifically:
@@ -133,6 +155,7 @@ console.log(stats)
133
155
  // maxRenderTime: 8.32,
134
156
  // minRenderTime: 0.12,
135
157
  // lastRenderTime: 1.23,
158
+ // timingCompensationMs: 4.8,
136
159
  // renderFps: 60,
137
160
  // usingWorker: true,
138
161
  // offscreenRender: true,
@@ -155,22 +178,23 @@ console.log(`Events: ${eventCount}, Styles: ${styleCount}`)
155
178
 
156
179
  **Stats Reference:**
157
180
 
158
- | Property | Type | Description |
159
- |----------|------|-------------|
160
- | `framesRendered` | number | Total frames rendered since reset |
161
- | `framesDropped` | number | Frames dropped due to slow rendering |
162
- | `avgRenderTime` | number | Average render time in milliseconds |
163
- | `maxRenderTime` | number | Maximum render time in milliseconds |
164
- | `minRenderTime` | number | Minimum render time in milliseconds |
165
- | `lastRenderTime` | number | Most recent render time in milliseconds |
166
- | `renderFps` | number | Estimated render FPS based on timing |
167
- | `usingWorker` | boolean | Whether using Web Worker |
168
- | `offscreenRender` | boolean | Whether offscreen rendering is enabled |
169
- | `onDemandRender` | boolean | Whether on-demand rendering is enabled |
170
- | `pendingRenders` | number | Number of pending render operations |
171
- | `totalEvents` | number | Total subtitle events in current track |
172
- | `cacheHits` | number | Number of cache hits (unchanged frames) |
173
- | `cacheMisses` | number | Number of cache misses (rendered frames) |
181
+ | Property | Type | Description |
182
+ | ---------------------- | ------- | ----------------------------------------------------------------------- |
183
+ | `framesRendered` | number | Total frames rendered since reset |
184
+ | `framesDropped` | number | Frames dropped due to slow rendering |
185
+ | `avgRenderTime` | number | Average render time in milliseconds |
186
+ | `maxRenderTime` | number | Maximum render time in milliseconds |
187
+ | `minRenderTime` | number | Minimum render time in milliseconds |
188
+ | `lastRenderTime` | number | Most recent render time in milliseconds |
189
+ | `timingCompensationMs` | number | Automatically learned presentation-latency compensation in milliseconds |
190
+ | `renderFps` | number | Estimated render FPS based on timing |
191
+ | `usingWorker` | boolean | Whether using Web Worker |
192
+ | `offscreenRender` | boolean | Whether offscreen rendering is enabled |
193
+ | `onDemandRender` | boolean | Whether on-demand rendering is enabled |
194
+ | `pendingRenders` | number | Number of pending render operations |
195
+ | `totalEvents` | number | Total subtitle events in current track |
196
+ | `cacheHits` | number | Number of cache hits (unchanged frames) |
197
+ | `cacheMisses` | number | Number of cache misses (rendered frames) |
174
198
 
175
199
  ## GPU Rendering
176
200
 
@@ -198,173 +222,180 @@ if (renderer.isUsingGPURenderer) {
198
222
 
199
223
  The default options are best, and automatically fallback to the next fastest options in line, when the API's they use are unsupported. You can however forcefully change this behavior by specifying options.
200
224
 
201
- | Option | Type | Default | Description |
202
- |--------|------|---------|-------------|
203
- | `video` | HTMLVideoElement | - | Video to use as target for rendering and event listeners |
204
- | `canvas` | HTMLCanvasElement | - | Canvas to use for manual handling (optional if video is provided) |
205
- | `blendMode` | `'js'` \| `'wasm'` | `'wasm'` | Image blending mode. WASM is better for low-end devices, JS for hardware acceleration |
206
- | `asyncRender` | boolean | auto | Render via ImageBitmap. Defaults to `true` on Canvas2D paths and `false` when a GPU renderer is active (raw buffers upload with fewer copies) or on WebKit |
207
- | `offscreenRender` | boolean | `true` | Render fully on the worker, greatly reduces CPU usage |
208
- | `onDemandRender` | boolean | `true` | Render subtitles as the video player renders frames |
209
- | `targetFps` | number | `24` | Target FPS when not using onDemandRender |
210
- | `timeOffset` | number | `0` | Subtitle time offset in seconds |
211
- | `debug` | boolean | `false` | Enable debug logging |
212
- | `prescaleFactor` | number | `1.0` | Scale factor for subtitles canvas |
213
- | `prescaleHeightLimit` | number | `1080` | Height limit for prescaling in pixels |
214
- | `maxRenderHeight` | number | `0` | Maximum render height (0 = no limit) |
215
- | `dropAllAnimations` | boolean | `false` | Discard all animated tags for performance |
216
- | `dropAllBlur` | boolean | `false` | Drop all blur effects (~10x performance gain) |
217
- | `clampPos` | boolean | `false` | Clamp `\pos` values to script resolution |
218
- | `renderAhead` | number | `0.008` | Extra seconds to render ahead, compensates pipeline latency |
219
- | `workerUrl` | string | `'akarisub-worker.js'` | URL to the worker script |
220
- | `wasmUrl` | string | `'akarisub-worker.wasm'` | URL to the WASM binary |
221
- | `subUrl` | string | - | URL of the subtitle file to play |
222
- | `subContent` | string \| Uint8Array \| ArrayBuffer | - | Content of the subtitle file to play |
223
- | `encryptedSubContent` | EncryptedSubtitleContent | - | AES-GCM encrypted subtitle payload, decrypted inside the worker |
224
- | `fonts` | (string \| Uint8Array)[] | - | Array of font URLs or Uint8Arrays to force load |
225
- | `availableFonts` | Record<string, string \| Uint8Array> | `{'liberation sans': './default.woff2'}` | Available fonts map (lowercase name URL/data) |
226
- | `fallbackFonts` | string[] | `['liberation sans']` | Fallback font families in order, used for the fontconfig cascade |
227
- | `useLocalFonts` | boolean | `true` | Use Local Font Access API if available |
228
- | `libassMemoryLimit` | number | `128` | libass bitmap cache memory limit in MiB |
229
- | `libassGlyphLimit` | number | `2048` | libass glyph cache limit |
230
- | `fullTrackWarmup` | boolean | `false` | Pre-render early track windows after load to warm libass caches |
231
- | `onCanvasFallback` | function | - | Callback when no GPU renderer is available (Canvas2D fallback) |
225
+ | Option | Type | Default | Description |
226
+ | --------------------- | ------------------------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
227
+ | `video` | HTMLVideoElement | - | Video to use as target for rendering and event listeners |
228
+ | `canvas` | HTMLCanvasElement | - | Canvas to use for manual handling (optional if video is provided) |
229
+ | `blendMode` | `'js'` \| `'wasm'` | `'wasm'` | Image blending mode. WASM is better for low-end devices, JS for hardware acceleration |
230
+ | `asyncRender` | boolean | auto | Render via ImageBitmap. Defaults to `true` on Canvas2D paths and `false` when a GPU renderer is active (raw buffers upload with fewer copies) or on WebKit |
231
+ | `offscreenRender` | boolean | automatic | Render fully on the worker; enabled for video-managed canvases and disabled for custom canvases |
232
+ | `rawAssImageGpu` | boolean | `false` | Compose raw libass masks with worker WebGL2 when offscreen rendering is active |
233
+ | `onDemandRender` | boolean | `true` | Render subtitles as the video player renders frames |
234
+ | `adaptiveTiming` | boolean | `true` | Compensate measured queue, worker, bitmap, IPC, and paint latency while video is playing; pause and seek renders remain frame-exact |
235
+ | `frameTimeline` | FrameTimeline | - | Encoded browser-frame timestamps plus optional media/subtitle clock offsets; enables frame-locked libass sampling |
236
+ | `framePrefetch` | number | `2` | Number of exact subtitle-frame bitmaps to prepare ahead (`0` disables preparation, maximum `24`) |
237
+ | `targetFps` | number | `24` | Target FPS when not using onDemandRender |
238
+ | `timeOffset` | number | `0` | Subtitle time offset in seconds |
239
+ | `debug` | boolean | `false` | Enable debug logging |
240
+ | `prescaleFactor` | number | `1.0` | Scale factor for subtitles canvas |
241
+ | `prescaleHeightLimit` | number | `1080` | Height limit for prescaling in pixels |
242
+ | `maxRenderHeight` | number | `0` | Maximum render height (0 = no limit) |
243
+ | `dropAllAnimations` | boolean | `false` | Discard all animated tags for performance |
244
+ | `dropAllBlur` | boolean | `false` | Drop all blur effects (~10x performance gain) |
245
+ | `clampPos` | boolean | `false` | Clamp `\pos` values to script resolution |
246
+ | `renderAhead` | number | `0` | Optional extra seconds to render ahead, in addition to adaptive timing; also applies when `onDemandRender` is disabled |
247
+ | `workerUrl` | string | package worker URL | Optional worker script URL. Defaults to the package worker module URL |
248
+ | `wasmUrl` | string | package WASM URL | Optional WASM binary URL. Defaults to the URL resolved from `import.meta.url` |
249
+ | `glueUrl` | string | package glue URL | Optional WASM glue script URL. Defaults to the URL resolved from `import.meta.url` |
250
+ | `subUrl` | string | - | URL of the subtitle file to play |
251
+ | `subContent` | string \| Uint8Array \| ArrayBuffer | - | Content of the subtitle file to play |
252
+ | `encryptedSubContent` | EncryptedSubtitleContent | - | AES-GCM encrypted subtitle payload, decrypted inside the worker |
253
+ | `fonts` | (string \| Uint8Array)[] | - | Array of font URLs or Uint8Arrays to force load |
254
+ | `availableFonts` | Record<string, string \| Uint8Array> | liberation sans from package assets | Available fonts map (lowercase name → URL/data) |
255
+ | `fallbackFonts` | string[] | `['liberation sans']` | Fallback font families in order, used for the fontconfig cascade |
256
+ | `useLocalFonts` | boolean | `true` | Use Local Font Access API if available |
257
+ | `libassMemoryLimit` | number | `128` | libass bitmap cache memory limit in MiB |
258
+ | `libassGlyphLimit` | number | `2048` | libass glyph cache limit |
259
+ | `fullTrackWarmup` | boolean | `false` | Pre-render early track windows after load to warm libass caches |
260
+ | `onCanvasFallback` | function | - | Callback when no GPU renderer is available (Canvas2D fallback) |
232
261
 
233
262
  ## Methods
234
263
 
235
264
  ### Track Management
236
265
 
237
- | Method | Parameters | Description |
238
- |--------|------------|-------------|
239
- | `setTrackByUrl(url)` | `url: string` | Load subtitle from URL |
240
- | `setTrack(content)` | `content: string \| Uint8Array \| ArrayBuffer` | Set subtitle from content |
241
- | `setEncryptedTrack(content)` | `content: EncryptedSubtitleContent` | Set subtitle from an encrypted payload (decrypted in the worker) |
242
- | `freeTrack()` | - | Remove current subtitles |
266
+ | Method | Parameters | Description |
267
+ | ---------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- |
268
+ | `setTrackByUrl(url)` | `url: string` | Load subtitle from URL |
269
+ | `setTrack(content)` | `content: string \| Uint8Array \| ArrayBuffer` | Set subtitle from content |
270
+ | `setEncryptedTrack(content)` | `content: EncryptedSubtitleContent` | Set subtitle from an encrypted payload (decrypted in the worker) |
271
+ | `freeTrack()` | - | Remove current subtitles |
243
272
 
244
273
  ### Playback Control
245
274
 
246
- | Method | Parameters | Description |
247
- |--------|------------|-------------|
248
- | `setIsPaused(isPaused)` | `isPaused: boolean` | Set playback pause state |
249
- | `setRate(rate)` | `rate: number` | Set playback rate (speed multiplier) |
250
- | `setCurrentTime(isPaused?, currentTime?, rate?)` | `isPaused?: boolean, currentTime?: number, rate?: number` | Set current time, playback state and rate |
275
+ | Method | Parameters | Description |
276
+ | ------------------------------------------------ | --------------------------------------------------------- | --------------------------------------------- |
277
+ | `setIsPaused(isPaused)` | `isPaused: boolean` | Set playback pause state |
278
+ | `setRate(rate)` | `rate: number` | Set playback rate (speed multiplier) |
279
+ | `setCurrentTime(isPaused?, currentTime?, rate?)` | `isPaused?: boolean, currentTime?: number, rate?: number` | Set current time, playback state and rate |
280
+ | `setFrameTimeline(frameTimes)` | `ArrayLike<number> \| null` | Replace or disable the encoded-frame timeline |
251
281
 
252
282
  ### Video & Canvas
253
283
 
254
- | Method | Parameters | Description |
255
- |--------|------------|-------------|
256
- | `setVideo(video)` | `video: HTMLVideoElement` | Change target video element |
257
- | `resize(width?, height?, top?, left?, force?)` | `width?: number, height?: number, top?: number, left?: number, force?: boolean` | Resize the canvas |
284
+ | Method | Parameters | Description |
285
+ | ---------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------- |
286
+ | `setVideo(video)` | `video: HTMLVideoElement` | Change target video element |
287
+ | `resize(width?, height?, top?, left?, force?)` | `width?: number, height?: number, top?: number, left?: number, force?: boolean` | Resize the canvas |
258
288
 
259
289
  ### Event Management
260
290
 
261
- | Method | Parameters | Returns | Description |
262
- |--------|------------|---------|-------------|
263
- | `createEvent(event)` | `event: Partial<ASSEvent>` | `void` | Create a new ASS event |
264
- | `setEvent(event, index)` | `event: Partial<ASSEvent>, index: number` | `void` | Overwrite event at index |
265
- | `removeEvent(index)` | `index: number` | `void` | Remove event at index |
266
- | `getEvents()` | - | `Promise<ASSEvent[]>` | Get all ASS events |
267
- | `getEventCount()` | - | `Promise<number>` | Get event count (lightweight) |
291
+ | Method | Parameters | Returns | Description |
292
+ | ------------------------ | ----------------------------------------- | --------------------- | ----------------------------- |
293
+ | `createEvent(event)` | `event: Partial<ASSEvent>` | `void` | Create a new ASS event |
294
+ | `setEvent(event, index)` | `event: Partial<ASSEvent>, index: number` | `void` | Overwrite event at index |
295
+ | `removeEvent(index)` | `index: number` | `void` | Remove event at index |
296
+ | `getEvents()` | - | `Promise<ASSEvent[]>` | Get all ASS events |
297
+ | `getEventCount()` | - | `Promise<number>` | Get event count (lightweight) |
268
298
 
269
299
  ### Style Management
270
300
 
271
- | Method | Parameters | Returns | Description |
272
- |--------|------------|---------|-------------|
273
- | `createStyle(style)` | `style: Partial<ASSStyle>` | `void` | Create a new ASS style |
274
- | `setStyle(style, index)` | `style: Partial<ASSStyle>, index: number` | `void` | Overwrite style at index |
275
- | `removeStyle(index)` | `index: number` | `void` | Remove style at index |
276
- | `getStyles()` | - | `Promise<ASSStyle[]>` | Get all ASS styles |
277
- | `getStyleCount()` | - | `Promise<number>` | Get style count (lightweight) |
278
- | `styleOverride(style)` | `style: Partial<ASSStyle>` | `void` | Set a style override |
279
- | `disableStyleOverride()` | - | `void` | Disable style override |
301
+ | Method | Parameters | Returns | Description |
302
+ | ------------------------ | ----------------------------------------- | --------------------- | ----------------------------- |
303
+ | `createStyle(style)` | `style: Partial<ASSStyle>` | `void` | Create a new ASS style |
304
+ | `setStyle(style, index)` | `style: Partial<ASSStyle>, index: number` | `void` | Overwrite style at index |
305
+ | `removeStyle(index)` | `index: number` | `void` | Remove style at index |
306
+ | `getStyles()` | - | `Promise<ASSStyle[]>` | Get all ASS styles |
307
+ | `getStyleCount()` | - | `Promise<number>` | Get style count (lightweight) |
308
+ | `styleOverride(style)` | `style: Partial<ASSStyle>` | `void` | Set a style override |
309
+ | `disableStyleOverride()` | - | `void` | Disable style override |
280
310
 
281
311
  ### Font Management
282
312
 
283
- | Method | Parameters | Description |
284
- |--------|------------|-------------|
285
- | `addFont(font)` | `font: string \| Uint8Array` | Add a font to the renderer |
286
- | `setDefaultFont(font)` | `font: string` | Change the default font family |
313
+ | Method | Parameters | Description |
314
+ | ---------------------- | ---------------------------- | ------------------------------ |
315
+ | `addFont(font)` | `font: string \| Uint8Array` | Add a font to the renderer |
316
+ | `setDefaultFont(font)` | `font: string` | Change the default font family |
287
317
 
288
318
  ### Statistics & Debugging
289
319
 
290
- | Method | Parameters | Returns | Description |
291
- |--------|------------|---------|-------------|
292
- | `getStats()` | - | `Promise<PerformanceStats>` | Get performance statistics |
293
- | `resetStats()` | - | `Promise<void>` | Reset statistics counters |
294
- | `getEventCount()` | - | `Promise<number>` | Get event count (lightweight) |
295
- | `getStyleCount()` | - | `Promise<number>` | Get style count (lightweight) |
320
+ | Method | Parameters | Returns | Description |
321
+ | ----------------- | ---------- | --------------------------- | ----------------------------- |
322
+ | `getStats()` | - | `Promise<PerformanceStats>` | Get performance statistics |
323
+ | `resetStats()` | - | `Promise<void>` | Reset statistics counters |
324
+ | `getEventCount()` | - | `Promise<number>` | Get event count (lightweight) |
325
+ | `getStyleCount()` | - | `Promise<number>` | Get style count (lightweight) |
296
326
 
297
327
  ### Lifecycle
298
328
 
299
- | Method | Parameters | Description |
300
- |--------|------------|-------------|
301
- | `destroy(err?)` | `err?: Error \| string` | Destroy the renderer and cleanup |
302
- | `sendMessage(target, data?, transferable?)` | `target: string, data?: Record<string, any>, transferable?: Transferable[]` | Send data to worker |
329
+ | Method | Parameters | Description |
330
+ | ------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------- |
331
+ | `destroy(err?)` | `err?: Error \| string` | Destroy the renderer and cleanup |
332
+ | `sendMessage(target, data?, transferable?)` | `target: string, data?: Record<string, any>, transferable?: Transferable[]` | Send data to worker |
303
333
 
304
334
  ## Properties
305
335
 
306
- | Property | Type | Description |
307
- |----------|------|-------------|
308
- | `debug` | boolean | Enable/disable debug logging |
309
- | `prescaleFactor` | number | Scale factor for subtitles |
310
- | `prescaleHeightLimit` | number | Height limit for prescaling |
311
- | `maxRenderHeight` | number | Maximum render height |
312
- | `timeOffset` | number | Subtitle time offset in seconds |
313
- | `renderAhead` | number | Extra seconds to render ahead of the video clock |
314
- | `busy` | boolean | Whether the renderer is currently busy |
315
- | `rendererType` | `'webgpu'` \| `'webgl2'` \| `'canvas2d'` | Active renderer backend (read-only) |
316
- | `isUsingGPURenderer` | boolean | Whether a hardware-accelerated renderer is active (read-only) |
317
- | `isUsingWebGPU` | boolean | *Deprecated* - use `rendererType === 'webgpu'` |
336
+ | Property | Type | Description |
337
+ | --------------------- | ---------------------------------------- | ------------------------------------------------------------- |
338
+ | `debug` | boolean | Enable/disable debug logging |
339
+ | `prescaleFactor` | number | Scale factor for subtitles |
340
+ | `prescaleHeightLimit` | number | Height limit for prescaling |
341
+ | `maxRenderHeight` | number | Maximum render height |
342
+ | `timeOffset` | number | Subtitle time offset in seconds |
343
+ | `renderAhead` | number | Optional extra seconds to render ahead of the video clock |
344
+ | `framePrefetch` | number | Number of exact subtitle frames prepared ahead |
345
+ | `busy` | boolean | Whether the renderer is currently busy |
346
+ | `rendererType` | `'webgpu'` \| `'webgl2'` \| `'canvas2d'` | Active renderer backend (read-only) |
347
+ | `isUsingGPURenderer` | boolean | Whether a hardware-accelerated renderer is active (read-only) |
348
+ | `isUsingWebGPU` | boolean | _Deprecated_ - use `rendererType === 'webgpu'` |
318
349
 
319
350
  ## Type Definitions
320
351
 
321
352
  ### ASSEvent
322
353
 
323
- | Property | Type | Description |
324
- |----------|------|-------------|
325
- | `Start` | number | Start time in milliseconds |
326
- | `Duration` | number | Duration in milliseconds |
327
- | `Style` | string | Style name |
328
- | `Name` | string | Character name (informational) |
329
- | `MarginL` | number | Left margin override in pixels |
330
- | `MarginR` | number | Right margin override in pixels |
331
- | `MarginV` | number | Bottom margin override in pixels |
332
- | `Effect` | string | Transition effect |
333
- | `Text` | string | Subtitle text content |
334
- | `ReadOrder` | number | Read order number |
335
- | `Layer` | number | Z-index layer |
336
- | `_index` | number | Internal index (optional) |
354
+ | Property | Type | Description |
355
+ | ----------- | ------ | -------------------------------- |
356
+ | `Start` | number | Start time in milliseconds |
357
+ | `Duration` | number | Duration in milliseconds |
358
+ | `Style` | string | Style name |
359
+ | `Name` | string | Character name (informational) |
360
+ | `MarginL` | number | Left margin override in pixels |
361
+ | `MarginR` | number | Right margin override in pixels |
362
+ | `MarginV` | number | Bottom margin override in pixels |
363
+ | `Effect` | string | Transition effect |
364
+ | `Text` | string | Subtitle text content |
365
+ | `ReadOrder` | number | Read order number |
366
+ | `Layer` | number | Z-index layer |
367
+ | `_index` | number | Internal index (optional) |
337
368
 
338
369
  ### ASSStyle
339
370
 
340
- | Property | Type | Description |
341
- |----------|------|-------------|
342
- | `Name` | string | Style name (case sensitive) |
343
- | `FontName` | string | Font family name |
344
- | `FontSize` | number | Font size |
345
- | `PrimaryColour` | number | Primary color (RGBA as uint32) |
346
- | `SecondaryColour` | number | Secondary color (RGBA as uint32) |
347
- | `OutlineColour` | number | Outline color (RGBA as uint32) |
348
- | `BackColour` | number | Background/shadow color (RGBA as uint32) |
349
- | `Bold` | number | Bold (-1 = true, 0 = false) |
350
- | `Italic` | number | Italic (-1 = true, 0 = false) |
351
- | `Underline` | number | Underline (-1 = true, 0 = false) |
352
- | `StrikeOut` | number | StrikeOut (-1 = true, 0 = false) |
353
- | `ScaleX` | number | Width scale (percent) |
354
- | `ScaleY` | number | Height scale (percent) |
355
- | `Spacing` | number | Extra spacing between characters (pixels) |
356
- | `Angle` | number | Rotation angle (degrees) |
357
- | `BorderStyle` | number | Border style (1 = outline + shadow, 3 = opaque box) |
358
- | `Outline` | number | Outline width (0-4 pixels) |
359
- | `Shadow` | number | Shadow depth (0-4 pixels) |
360
- | `Alignment` | number | Alignment (1-9, numpad style) |
361
- | `MarginL` | number | Left margin (pixels) |
362
- | `MarginR` | number | Right margin (pixels) |
363
- | `MarginV` | number | Vertical margin (pixels) |
364
- | `Encoding` | number | Font encoding |
365
- | `treat_fontname_as_pattern` | number | Treat font name as pattern |
366
- | `Blur` | number | Blur amount |
367
- | `Justify` | number | Text justification |
371
+ | Property | Type | Description |
372
+ | --------------------------- | ------ | --------------------------------------------------- |
373
+ | `Name` | string | Style name (case sensitive) |
374
+ | `FontName` | string | Font family name |
375
+ | `FontSize` | number | Font size |
376
+ | `PrimaryColour` | number | Primary color (RGBA as uint32) |
377
+ | `SecondaryColour` | number | Secondary color (RGBA as uint32) |
378
+ | `OutlineColour` | number | Outline color (RGBA as uint32) |
379
+ | `BackColour` | number | Background/shadow color (RGBA as uint32) |
380
+ | `Bold` | number | Bold (-1 = true, 0 = false) |
381
+ | `Italic` | number | Italic (-1 = true, 0 = false) |
382
+ | `Underline` | number | Underline (-1 = true, 0 = false) |
383
+ | `StrikeOut` | number | StrikeOut (-1 = true, 0 = false) |
384
+ | `ScaleX` | number | Width scale (percent) |
385
+ | `ScaleY` | number | Height scale (percent) |
386
+ | `Spacing` | number | Extra spacing between characters (pixels) |
387
+ | `Angle` | number | Rotation angle (degrees) |
388
+ | `BorderStyle` | number | Border style (1 = outline + shadow, 3 = opaque box) |
389
+ | `Outline` | number | Outline width (0-4 pixels) |
390
+ | `Shadow` | number | Shadow depth (0-4 pixels) |
391
+ | `Alignment` | number | Alignment (1-9, numpad style) |
392
+ | `MarginL` | number | Left margin (pixels) |
393
+ | `MarginR` | number | Right margin (pixels) |
394
+ | `MarginV` | number | Vertical margin (pixels) |
395
+ | `Encoding` | number | Font encoding |
396
+ | `treat_fontname_as_pattern` | number | Treat font name as pattern |
397
+ | `Blur` | number | Blur amount |
398
+ | `Justify` | number | Text justification |
368
399
 
369
400
  # How to build?
370
401
 
@@ -395,10 +426,10 @@ git clone --recursive https://github.com/altqx/akarisub.git
395
426
  ```bash
396
427
  mise install # installs emsdk, bun, cmake
397
428
  bun install # JS dependencies
398
- make # builds the static libs (fribidi, freetype, harfbuzz, fontconfig, libass, ...) and the WASM worker
399
- bun run build # builds the WASM worker, TypeScript declarations and JS bundles
429
+ make # builds the static libs (fribidi, freetype, harfbuzz, fontconfig, libass, ...) and the WASM glue
430
+ bun run build # builds the WASM glue and TypeScript
400
431
  ```
401
432
 
402
433
  - If on macOS with libtool from brew, `LIBTOOLIZE=glibtoolize make`
403
- - Incremental rebuilds of the worker only: `bun run build:wasm` (or `make worker`)
404
- - Artifacts are in `dist/`
434
+ - Incremental rebuilds of the WASM glue only: `bun run build:wasm` (or `make worker`)
435
+ - Artifacts are in `pkg/` (WASM glue and binary) and `dist/` (TypeScript)