akarisub 1.0.0 → 1.1.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.
- package/README.md +217 -55
- package/dist/COPYRIGHT +3 -3
- package/dist/index.d.ts +23 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/ts/akarisub.d.ts +274 -2
- package/dist/ts/akarisub.d.ts.map +1 -1
- package/dist/ts/akarisub.js +1116 -80
- package/dist/ts/akarisub.js.map +1 -1
- package/dist/ts/asset-loader.d.ts +45 -0
- package/dist/ts/asset-loader.d.ts.map +1 -0
- package/dist/ts/asset-loader.js +297 -0
- package/dist/ts/asset-loader.js.map +1 -0
- package/dist/ts/color-space.d.ts +86 -0
- package/dist/ts/color-space.d.ts.map +1 -0
- package/dist/ts/color-space.js +311 -0
- package/dist/ts/color-space.js.map +1 -0
- package/dist/ts/cue-events.d.ts +21 -0
- package/dist/ts/cue-events.d.ts.map +1 -0
- package/dist/ts/cue-events.js +79 -0
- package/dist/ts/cue-events.js.map +1 -0
- package/dist/ts/font-subsets.d.ts +27 -0
- package/dist/ts/font-subsets.d.ts.map +1 -0
- package/dist/ts/font-subsets.js +117 -0
- package/dist/ts/font-subsets.js.map +1 -0
- package/dist/ts/streaming.d.ts +4 -0
- package/dist/ts/streaming.d.ts.map +1 -0
- package/dist/ts/streaming.js +39 -0
- package/dist/ts/streaming.js.map +1 -0
- package/dist/ts/types.d.ts +359 -7
- package/dist/ts/types.d.ts.map +1 -1
- package/dist/ts/unicode-scripts.d.ts +19 -0
- package/dist/ts/unicode-scripts.d.ts.map +1 -0
- package/dist/ts/unicode-scripts.js +241 -0
- package/dist/ts/unicode-scripts.js.map +1 -0
- package/dist/ts/utils.d.ts +22 -11
- package/dist/ts/utils.d.ts.map +1 -1
- package/dist/ts/utils.js +9 -44
- package/dist/ts/utils.js.map +1 -1
- package/dist/ts/video-frame.d.ts +34 -0
- package/dist/ts/video-frame.d.ts.map +1 -0
- package/dist/ts/video-frame.js +75 -0
- package/dist/ts/video-frame.js.map +1 -0
- package/dist/ts/wasm-capabilities.d.ts +30 -0
- package/dist/ts/wasm-capabilities.d.ts.map +1 -0
- package/dist/ts/wasm-capabilities.js +64 -0
- package/dist/ts/wasm-capabilities.js.map +1 -0
- package/dist/ts/wasm.d.ts +4 -0
- package/dist/ts/wasm.d.ts.map +1 -1
- package/dist/ts/wasm.js +24 -0
- package/dist/ts/wasm.js.map +1 -1
- package/dist/ts/webgl2-renderer.d.ts +36 -3
- package/dist/ts/webgl2-renderer.d.ts.map +1 -1
- package/dist/ts/webgl2-renderer.js +100 -16
- package/dist/ts/webgl2-renderer.js.map +1 -1
- package/dist/ts/webgpu-renderer.d.ts +40 -0
- package/dist/ts/webgpu-renderer.d.ts.map +1 -1
- package/dist/ts/webgpu-renderer.js +123 -18
- package/dist/ts/webgpu-renderer.js.map +1 -1
- package/dist/ts/worker.js +729 -112
- package/dist/ts/worker.js.map +1 -1
- package/dist/wrapper.d.ts +10 -3
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +8 -2
- package/dist/wrapper.js.map +1 -1
- package/package.json +4 -4
- package/pkg/akarisub-mt.js +70 -0
- package/pkg/akarisub-mt.wasm +0 -0
- package/pkg/akarisub.js +48 -47
- package/pkg/akarisub.wasm +0 -0
- package/src/wrapper.ts +54 -2
package/README.md
CHANGED
|
@@ -14,14 +14,17 @@ AkariSub is a JS wrapper for <a href="https://github.com/libass/libass">libass</
|
|
|
14
14
|
- Supports most SSA/ASS features (everything libass supports)
|
|
15
15
|
- Supports all OpenType, TrueType and WOFF fonts, as well as embedded fonts
|
|
16
16
|
- Supports anamorphic videos [(on browsers which support it)](https://caniuse.com/mdn-api_htmlvideoelement_requestvideoframecallback)
|
|
17
|
-
- Supports different video color spaces [(on browsers which support it)](https://caniuse.com/mdn-api_videocolorspace)
|
|
17
|
+
- Supports different video color spaces, including HDR / wide-gamut overlay [(on browsers which support it)](https://caniuse.com/mdn-api_videocolorspace)
|
|
18
18
|
- Capable of using local fonts [(on browsers which support it)](https://caniuse.com/mdn-api_window_querylocalfonts)
|
|
19
19
|
- Works fast (all the heavy lifting is done by WebAssembly)
|
|
20
|
-
- Is fully threaded (
|
|
20
|
+
- Is fully threaded (worker plus optional WASM pthreads on isolated pages)
|
|
21
21
|
- Is asynchronous (renders when available, not in order of execution)
|
|
22
22
|
- Benefits from hardware acceleration (uses hardware accelerated canvas API's)
|
|
23
23
|
- Doesn't manipulate the DOM to render subtitles
|
|
24
24
|
- Easy to use - just connect it to video element
|
|
25
|
+
- Optional WebCodecs `VideoFrame` clock for custom players and editors
|
|
26
|
+
- Streaming / live tracks: append ASS fragments, Matroska packets, or events without reloading the file
|
|
27
|
+
- Font subset lazy loading: fetch only the unicode-range slices the current script needs (CJK fonts in particular)
|
|
25
28
|
|
|
26
29
|
### Fork Enhancements
|
|
27
30
|
|
|
@@ -30,7 +33,13 @@ AkariSub is a JS wrapper for <a href="https://github.com/libass/libass">libass</
|
|
|
30
33
|
- **Proper Fontconfig Implementation** - add Fontconfig support with multiple fallback fonts supported
|
|
31
34
|
- **Encrypted Subtitles** - optionally load AES-GCM encrypted subtitle payloads that are decrypted inside the worker, so plaintext never touches the main thread
|
|
32
35
|
- **Statistics Reporting** - Built-in statistics and performance metrics for debugging and monitoring
|
|
36
|
+
- **Atomic Track Switching** - `preloadTrack()` / `activatePreloadedTrack()` load a second language track and its fonts before swapping, so the last frame stays visible
|
|
37
|
+
- **Streaming / live tracks** - `initStreamingTrack()` plus `appendSubtitleData()` / `appendSubtitleChunk()` accept HLS fragments and Matroska packets without a full file
|
|
38
|
+
- **Font subset lazy loading** - `availableFonts` can list unicode-range slices so CJK (and other) glyph files load only when the track needs them
|
|
39
|
+
- **Cue Callbacks** - `onCueEnter`, `onCueExit`, `onRender`, `onRendererChange`, and `onPerformanceWarning` for overlays and analytics without polling `getEvents()`
|
|
33
40
|
- **TypeScript Support** - Full TypeScript definitions and type safety
|
|
41
|
+
- **HDR / Wide Color Gamut** - Matches Display P3 / Rec.2020 canvases and PQ/HLG video, and converts BT.2020 YCbCr matrices
|
|
42
|
+
- **WASM SIMD + pthreads** - SIMD libass kernels in the default binary; optional `akarisub-mt.wasm` blends independent regions on isolated pages
|
|
34
43
|
- **Updated Dependencies** - All dependencies updated to their latest versions, including libass
|
|
35
44
|
|
|
36
45
|
## Installation
|
|
@@ -116,6 +125,39 @@ If the browser timeline is normalized from decode time rather than the first dis
|
|
|
116
125
|
|
|
117
126
|
`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
127
|
|
|
128
|
+
## Using with WebCodecs
|
|
129
|
+
|
|
130
|
+
Apps that decode with `VideoDecoder` instead of `HTMLVideoElement` can drive the same on-demand and frame-timeline path with each output `VideoFrame`. Pass a canvas, keep `onDemandRender` enabled, and present frames as they leave the decoder. AkariSub reads `timestamp` (microseconds), `displayWidth` / `displayHeight`, and `colorSpace` (`matrix`, `primaries`, `transfer`). HDR frames pick a Display P3 or Rec.2020 canvas when the browser can create one. It does not take ownership of the frame.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import AkariSub, { frameTimelineFromTimestamps } from 'akarisub'
|
|
134
|
+
|
|
135
|
+
const renderer = new AkariSub({
|
|
136
|
+
canvas: document.querySelector('canvas'),
|
|
137
|
+
subUrl: './tracks/sub.ass',
|
|
138
|
+
frameTimeline: frameTimelineFromTimestamps(packetTimestampsUs),
|
|
139
|
+
onDemandRender: true
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const decoder = new VideoDecoder({
|
|
143
|
+
output(frame) {
|
|
144
|
+
renderer.presentVideoFrame(frame, {
|
|
145
|
+
expectedDisplayTime: performance.now(),
|
|
146
|
+
isPaused: false,
|
|
147
|
+
rate: 1
|
|
148
|
+
})
|
|
149
|
+
frame.close()
|
|
150
|
+
},
|
|
151
|
+
error(err) {
|
|
152
|
+
console.error(err)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Paused editor and offline-preview frames pass `isPaused: true` so libass samples that exact timestamp. `setVideoColorSpace()` can apply a matrix without presenting a frame. `frameTimelineFromVideoFrames()` builds a timeline from already-decoded frames.
|
|
158
|
+
|
|
159
|
+
In browsers without `requestVideoFrameCallback`, set `onDemandRender: true` explicitly so `presentVideoFrame` uses the demand path instead of the worker RAF loop.
|
|
160
|
+
|
|
119
161
|
## Changing subtitles
|
|
120
162
|
|
|
121
163
|
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:
|
|
@@ -129,6 +171,79 @@ You're not limited to only display the subtitle file you referenced in your opti
|
|
|
129
171
|
renderer.setTrackByUrl('/newsub.ass')
|
|
130
172
|
```
|
|
131
173
|
|
|
174
|
+
For streaming players with multiple language tracks, preload the next file so the swap does not drop a frame or hitch on font load. The last painted frame stays on screen until the new track's first frame is ready:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const ja = await renderer.preloadTrack({ kind: 'url', url: '/subs/ja.ass' })
|
|
178
|
+
await renderer.activatePreloadedTrack(ja.id)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`preloadTrack()` also accepts ASS text or bytes, `{ kind: 'content', content }`, or `{ kind: 'encrypted', content }`. `activatePreloadedTrack()` without an id uses the most recently preloaded track.
|
|
182
|
+
|
|
183
|
+
## Streaming and live tracks
|
|
184
|
+
|
|
185
|
+
Live streams and HLS fragments rarely have a complete ASS file up front. Initialize a header-only track, then append events as packets arrive. libass keeps styles and script info; you decide how long past cues stay in memory.
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
const renderer = new AkariSub({
|
|
189
|
+
video,
|
|
190
|
+
// Header only: Script Info + Styles, no Dialogue lines required
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
renderer.initStreamingTrack({
|
|
194
|
+
header: `[Script Info]
|
|
195
|
+
ScriptType: v4.00+
|
|
196
|
+
PlayResX: 1920
|
|
197
|
+
PlayResY: 1080
|
|
198
|
+
|
|
199
|
+
[V4+ Styles]
|
|
200
|
+
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
|
201
|
+
Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
|
|
202
|
+
|
|
203
|
+
[Events]
|
|
204
|
+
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
205
|
+
`,
|
|
206
|
+
pruneDelay: 30
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// HLS WebVTT/ASS fragment converted to Dialogue lines:
|
|
210
|
+
renderer.appendSubtitleData('Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello live\n')
|
|
211
|
+
|
|
212
|
+
// Or structured events (Start/Duration are libass milliseconds, same as createEvent):
|
|
213
|
+
renderer.appendEvents([{ Start: 4000, Duration: 2000, Style: 'Default', Text: 'Next cue' }])
|
|
214
|
+
|
|
215
|
+
// Matroska/WebM live packets:
|
|
216
|
+
renderer.initStreamingTrack({ header: codecPrivate, format: 'matroska', pruneDelay: 15 })
|
|
217
|
+
renderer.appendSubtitleChunk(packet, startSeconds, durationSeconds)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`pruneDelay` deletes events that ended more than that many seconds before the last rendered timestamp. Call `pruneEvents(video.currentTime - 30)` yourself when you want an explicit live window. `flushEvents()` drops every event and keeps the header.
|
|
221
|
+
|
|
222
|
+
Do not mix `appendSubtitleChunk` with `appendSubtitleData` / `createEvent` on the same track. libass uses ReadOrder to drop duplicate Matroska packets, and that check breaks if the event list is edited some other way.
|
|
223
|
+
|
|
224
|
+
## Font subsetting / lazy loading
|
|
225
|
+
|
|
226
|
+
A full CJK font is often 10-20MB. Split it into unicode-range files (the same files you would ship for CSS `@font-face`) and list them under `availableFonts`. AkariSub reads the current track, keeps Latin loaded, and fetches Hiragana/Katakana/Han/Hangul (or other scripts) only when those glyphs appear, including as live events arrive.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
const renderer = new AkariSub({
|
|
230
|
+
video,
|
|
231
|
+
subUrl: './tracks/sub.ass',
|
|
232
|
+
availableFonts: {
|
|
233
|
+
'liberation sans': '/fonts/liberation-sans.woff2',
|
|
234
|
+
'noto sans cjk jp': [
|
|
235
|
+
{ src: '/fonts/noto-cjk-latin.woff2', unicodeRange: 'U+0000-00FF' },
|
|
236
|
+
{ src: '/fonts/noto-cjk-hira.woff2', scripts: ['hira'] },
|
|
237
|
+
{ src: '/fonts/noto-cjk-kana.woff2', scripts: ['kana'] },
|
|
238
|
+
{ src: '/fonts/noto-cjk-han.woff2', scripts: ['hani'] }
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
fallbackFonts: ['liberation sans']
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`scripts` accepts OpenType tags (`latn`, `hani`, `hira`, `kana`, `hang`) and aliases (`cjk`, `jp`, `kr`, `latin`). `fonts` still force-loads complete files. Set `lazyFonts: false` to fetch every listed slice when the family is first referenced.
|
|
246
|
+
|
|
132
247
|
## Cleaning up the object
|
|
133
248
|
|
|
134
249
|
After you're finished with rendering the subtitles. You need to call the `destroy()` method to correctly destroy the object.
|
|
@@ -200,6 +315,8 @@ console.log(`Events: ${eventCount}, Styles: ${styleCount}`)
|
|
|
200
315
|
|
|
201
316
|
AkariSub automatically picks the fastest available renderer: WebGPU → WebGL2 → Canvas2D. GPU renderers are used when no custom canvas is given and the browser supports them:
|
|
202
317
|
|
|
318
|
+
GPU failures are recovered automatically. AkariSub retains the last completed subtitle frame while rebuilding after WebGPU `device.lost` or WebGL2 `webglcontextlost`; it resumes the same backend after recovery, or switches to Canvas2D if rebuilding/restoration fails. Recovery notifications include `reason: 'device-lost' | 'context-lost'`.
|
|
319
|
+
|
|
203
320
|
```typescript
|
|
204
321
|
import AkariSub from 'akarisub'
|
|
205
322
|
|
|
@@ -222,69 +339,114 @@ if (renderer.isUsingGPURenderer) {
|
|
|
222
339
|
|
|
223
340
|
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.
|
|
224
341
|
|
|
225
|
-
| Option
|
|
226
|
-
|
|
|
227
|
-
| `video`
|
|
228
|
-
| `canvas`
|
|
229
|
-
| `blendMode`
|
|
230
|
-
| `asyncRender`
|
|
231
|
-
| `offscreenRender`
|
|
232
|
-
| `rawAssImageGpu`
|
|
233
|
-
| `onDemandRender`
|
|
234
|
-
| `adaptiveTiming`
|
|
235
|
-
| `frameTimeline`
|
|
236
|
-
| `framePrefetch`
|
|
237
|
-
| `targetFps`
|
|
238
|
-
| `timeOffset`
|
|
239
|
-
| `debug`
|
|
240
|
-
| `prescaleFactor`
|
|
241
|
-
| `prescaleHeightLimit`
|
|
242
|
-
| `maxRenderHeight`
|
|
243
|
-
| `dropAllAnimations`
|
|
244
|
-
| `dropAllBlur`
|
|
245
|
-
| `clampPos`
|
|
246
|
-
| `renderAhead`
|
|
247
|
-
| `workerUrl`
|
|
248
|
-
| `wasmUrl`
|
|
249
|
-
| `glueUrl`
|
|
250
|
-
| `
|
|
251
|
-
| `
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
342
|
+
| Option | Type | Default | Description |
|
|
343
|
+
| ---------------------- | ------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
344
|
+
| `video` | HTMLVideoElement | - | Video to use as target for rendering and event listeners. WebCodecs players pass `canvas` and call `presentVideoFrame` instead |
|
|
345
|
+
| `canvas` | HTMLCanvasElement | - | Canvas to use for manual handling (optional if video is provided) |
|
|
346
|
+
| `blendMode` | `'js'` \| `'wasm'` | `'wasm'` | Image blending mode. WASM is better for low-end devices, JS for hardware acceleration |
|
|
347
|
+
| `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 |
|
|
348
|
+
| `offscreenRender` | boolean | automatic | Render fully on the worker; enabled for video-managed canvases and disabled for custom canvases |
|
|
349
|
+
| `rawAssImageGpu` | boolean | `false` | Compose raw libass masks with worker WebGL2 when offscreen rendering is active |
|
|
350
|
+
| `onDemandRender` | boolean | `true` | Render subtitles as the video player or `presentVideoFrame` presents frames |
|
|
351
|
+
| `adaptiveTiming` | boolean | `true` | Compensate measured queue, worker, bitmap, IPC, and paint latency while video is playing; pause and seek renders remain frame-exact |
|
|
352
|
+
| `frameTimeline` | FrameTimeline | - | Encoded browser-frame timestamps plus optional media/subtitle clock offsets; enables frame-locked libass sampling |
|
|
353
|
+
| `framePrefetch` | number | `2` | Number of exact subtitle-frame bitmaps to prepare ahead (`0` disables preparation, maximum `24`) |
|
|
354
|
+
| `targetFps` | number | `24` | Target FPS when not using onDemandRender |
|
|
355
|
+
| `timeOffset` | number | `0` | Subtitle time offset in seconds |
|
|
356
|
+
| `debug` | boolean | `false` | Enable debug logging |
|
|
357
|
+
| `prescaleFactor` | number | `1.0` | Scale factor for subtitles canvas |
|
|
358
|
+
| `prescaleHeightLimit` | number | `1080` | Height limit for prescaling in pixels |
|
|
359
|
+
| `maxRenderHeight` | number | `0` | Maximum render height (0 = no limit) |
|
|
360
|
+
| `dropAllAnimations` | boolean | `false` | Discard all animated tags for performance |
|
|
361
|
+
| `dropAllBlur` | boolean | `false` | Drop all blur effects (~10x performance gain) |
|
|
362
|
+
| `clampPos` | boolean | `false` | Clamp `\pos` values to script resolution |
|
|
363
|
+
| `renderAhead` | number | `0` | Optional extra seconds to render ahead, in addition to adaptive timing; also applies when `onDemandRender` is disabled |
|
|
364
|
+
| `workerUrl` | string | package worker URL | Optional worker script URL. Defaults to the package worker module URL |
|
|
365
|
+
| `wasmUrl` | string | package WASM URL | Optional WASM binary URL. Defaults to the URL resolved from `import.meta.url` |
|
|
366
|
+
| `glueUrl` | string | package glue URL | Optional WASM glue script URL. Defaults to the URL resolved from `import.meta.url` |
|
|
367
|
+
| `modernWasmUrl` | string | - | Optional SIMD WASM URL used when the engine validates `v128` |
|
|
368
|
+
| `mtWasmUrl` | string | package `akarisub-mt.wasm` | Optional pthread SIMD WASM URL. Requires COOP/COEP (`crossOriginIsolated`) |
|
|
369
|
+
| `canvasColorSpace` | `'srgb'` \| `'display-p3'` \| `'rec2020'` \| `'auto'` | `'auto'` | Overlay canvas color space. `auto` follows the video primaries |
|
|
370
|
+
| `hdr` | boolean \| `'auto'` | `'auto'` | Request an HDR canvas when the video transfer is PQ or HLG |
|
|
371
|
+
| `subUrl` | string | - | URL of the subtitle file to play |
|
|
372
|
+
| `subContent` | string \| Uint8Array \| ArrayBuffer | - | Content of the subtitle file to play |
|
|
373
|
+
| `encryptedSubContent` | EncryptedSubtitleContent | - | AES-GCM encrypted subtitle payload, decrypted inside the worker |
|
|
374
|
+
| `fonts` | (string \| Uint8Array)[] | - | Font URLs or bytes to force-load in full |
|
|
375
|
+
| `availableFonts` | Record<string, FontFamilySource> | liberation sans from package assets | Family name (lowercase) → URL, bytes, or unicode-range / script slices. Slices load lazily from the current track |
|
|
376
|
+
| `lazyFonts` | boolean | `true` | When `availableFonts` entries declare slices, fetch only the ones the current scripts need |
|
|
377
|
+
| `fallbackFonts` | string[] | `['liberation sans']` | Fallback font families in order, used for the fontconfig cascade |
|
|
378
|
+
| `useLocalFonts` | boolean | `true` | Use Local Font Access API if available |
|
|
379
|
+
| `libassMemoryLimit` | number | `128` | libass bitmap cache memory limit in MiB |
|
|
380
|
+
| `libassGlyphLimit` | number | `2048` | libass glyph cache limit |
|
|
381
|
+
| `fullTrackWarmup` | boolean | `false` | Pre-render early track windows after load to warm libass caches |
|
|
382
|
+
| `onCanvasFallback` | function | - | Callback when no GPU renderer is available (Canvas2D fallback) |
|
|
383
|
+
| `onCueEnter` | `(cue) => void` | - | Dialogue event became active at the sampled media time |
|
|
384
|
+
| `onCueExit` | `(cue) => void` | - | Dialogue event is no longer active |
|
|
385
|
+
| `onRender` | `(event) => void` | - | Demand-frame render finished |
|
|
386
|
+
| `onRendererChange` | `(event) => void` | - | Compositor backend changed or recovered; GPU recovery includes `event.reason` |
|
|
387
|
+
| `onPerformanceWarning` | `(warning) => void` | - | Slow frame, dropped frames, a full demand queue, or GPU recovery |
|
|
261
388
|
|
|
262
389
|
## Methods
|
|
263
390
|
|
|
264
391
|
### Track Management
|
|
265
392
|
|
|
266
|
-
| Method
|
|
267
|
-
|
|
|
268
|
-
| `setTrackByUrl(url)`
|
|
269
|
-
| `setTrack(content)`
|
|
270
|
-
| `setEncryptedTrack(content)`
|
|
271
|
-
| `freeTrack()`
|
|
393
|
+
| Method | Parameters | Description |
|
|
394
|
+
| ----------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
395
|
+
| `setTrackByUrl(url)` | `url: string` | Load subtitle from URL |
|
|
396
|
+
| `setTrack(content)` | `content: string \| Uint8Array \| ArrayBuffer` | Set subtitle from content |
|
|
397
|
+
| `setEncryptedTrack(content)` | `content: EncryptedSubtitleContent` | Set subtitle from an encrypted payload (decrypted in the worker) |
|
|
398
|
+
| `freeTrack()` | - | Remove current subtitles |
|
|
399
|
+
| `initStreamingTrack(options?)` | `StreamingTrackOptions \| string \| bytes` | Create a header-only track that can accept later fragments |
|
|
400
|
+
| `appendSubtitleData(content)` | `content: string \| Uint8Array \| ArrayBuffer` | Parse ASS text (Dialogue lines, extra styles) into the current track |
|
|
401
|
+
| `appendSubtitleChunk(data, start, duration)` | `data`, `start: number`, `duration: number` | Append one Matroska packet; `start`/`duration` are seconds |
|
|
402
|
+
| `appendEvents(events)` | `events: Partial<ASSEvent>[]` | Append several Dialogue events in one message |
|
|
403
|
+
| `pruneEvents(before)` | `before: number` | Drop events that ended before `before` seconds |
|
|
404
|
+
| `flushEvents()` | - | Drop every event and keep styles / script info |
|
|
405
|
+
| `configurePrune(delay)` | `delay: number` | Auto-drop events that ended more than `delay` seconds before the last render |
|
|
406
|
+
| `preloadTrack(source)` | `PreloadTrackSource \| string \| bytes` | Parse a track and load its fonts without replacing the visible one |
|
|
407
|
+
| `activatePreloadedTrack(id?)` | `id?: number` | Atomically swap to a preloaded track; last frame stays until the first new paint |
|
|
272
408
|
|
|
273
409
|
### Playback Control
|
|
274
410
|
|
|
275
|
-
| Method | Parameters
|
|
276
|
-
| ------------------------------------------------ |
|
|
277
|
-
| `setIsPaused(isPaused)` | `isPaused: boolean`
|
|
278
|
-
| `setRate(rate)` | `rate: number`
|
|
279
|
-
| `setCurrentTime(isPaused?, currentTime?, rate?)` | `isPaused?: boolean, currentTime?: number, rate?: number`
|
|
280
|
-
| `setFrameTimeline(frameTimes)` | `ArrayLike<number> \| null`
|
|
411
|
+
| Method | Parameters | Description |
|
|
412
|
+
| ------------------------------------------------ | ----------------------------------------------------------- | --------------------------------------------- |
|
|
413
|
+
| `setIsPaused(isPaused)` | `isPaused: boolean` | Set playback pause state |
|
|
414
|
+
| `setRate(rate)` | `rate: number` | Set playback rate (speed multiplier) |
|
|
415
|
+
| `setCurrentTime(isPaused?, currentTime?, rate?)` | `isPaused?: boolean, currentTime?: number, rate?: number` | Set current time, playback state and rate |
|
|
416
|
+
| `setFrameTimeline(frameTimes)` | `ArrayLike<number> \| null` | Replace or disable the encoded-frame timeline |
|
|
417
|
+
| `presentVideoFrame(frame, options?)` | `frame: VideoFrame, options?: PresentVideoFrameOptions` | Present a WebCodecs frame as the video clock |
|
|
418
|
+
| `setVideoColorSpace(colorSpace)` | `BT709` \| `BT601` \| `BT2020` \| matrix name \| `colorSpace` \| `null` | Set the video YCbCr matrix and optional HDR primaries/transfer |
|
|
281
419
|
|
|
282
420
|
### Video & Canvas
|
|
283
421
|
|
|
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
|
|
422
|
+
| Method | Parameters | Description |
|
|
423
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
424
|
+
| `setVideo(video)` | `video: HTMLVideoElement` | Change target video element. Clears a WebCodecs frame clock |
|
|
425
|
+
| `resize(width?, height?, top?, left?, force?)` | `width?: number, height?: number, top?: number, left?: number, force?: boolean` | Resize the canvas |
|
|
426
|
+
|
|
427
|
+
### Cue and render callbacks
|
|
428
|
+
|
|
429
|
+
Option callbacks and matching `EventTarget` events (`cueEnter`, `cueExit`, `render`, `rendererChange`, `performanceWarning`) report subtitle-clock activity and compositor lifecycle changes. Use them for chapter markers, character overlays, or analytics instead of polling `getEvents()`:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const renderer = new AkariSub({
|
|
433
|
+
video,
|
|
434
|
+
subUrl: '/subs/en.ass',
|
|
435
|
+
onCueEnter: (cue) => showCharacter(cue.name, cue.text),
|
|
436
|
+
onCueExit: (cue) => hideCharacter(cue.index),
|
|
437
|
+
onRender: (event) => recordSubtitleFrame(event.time, event.renderTimeMs),
|
|
438
|
+
onPerformanceWarning: (warning) => {
|
|
439
|
+
if (warning.kind === 'slow-frame') console.warn('slow subtitle frame', warning.renderTimeMs)
|
|
440
|
+
if (warning.kind === 'renderer-recovery') console.warn('GPU recovery', warning.reason)
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
renderer.addEventListener('rendererChange', (event) => {
|
|
445
|
+
console.log('compositor', event.detail.rendererType, event.detail.reason)
|
|
446
|
+
})
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`CueEvent.start` and `CueEvent.duration` are seconds on the subtitle clock, matching `video.currentTime` plus `timeOffset`. Encrypted tracks omit `name` and `text`.
|
|
288
450
|
|
|
289
451
|
### Event Management
|
|
290
452
|
|
|
@@ -431,5 +593,5 @@ bun run build # builds the WASM glue and TypeScript
|
|
|
431
593
|
```
|
|
432
594
|
|
|
433
595
|
- If on macOS with libtool from brew, `LIBTOOLIZE=glibtoolize make`
|
|
434
|
-
- Incremental rebuilds of the WASM glue only: `bun run build:wasm` (or `make
|
|
596
|
+
- Incremental rebuilds of the WASM glue only: `bun run build:wasm` (or `make workers`)
|
|
435
597
|
- Artifacts are in `pkg/` (WASM glue and binary) and `dist/` (TypeScript)
|
package/dist/COPYRIGHT
CHANGED
|
@@ -24,7 +24,7 @@ Regenerate it with `bun run license:third-party` after dependency, submodule, fo
|
|
|
24
24
|
|
|
25
25
|
| Package | Version | License | Source |
|
|
26
26
|
| --- | --- | --- | --- |
|
|
27
|
-
| `@types/bun` | 1.
|
|
27
|
+
| `@types/bun` | 1.4.0 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped.git |
|
|
28
28
|
| `@types/emscripten` | 1.41.5 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped.git |
|
|
29
29
|
| `@types/node` | 25.0.9 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped.git |
|
|
30
30
|
| `@typescript/typescript-aix-ppc64` | 7.0.2 | Apache-2.0 | https://github.com/microsoft/TypeScript.git |
|
|
@@ -47,8 +47,8 @@ Regenerate it with `bun run license:third-party` after dependency, submodule, fo
|
|
|
47
47
|
| `@typescript/typescript-sunos-x64` | 7.0.2 | Apache-2.0 | https://github.com/microsoft/TypeScript.git |
|
|
48
48
|
| `@typescript/typescript-win32-arm64` | 7.0.2 | Apache-2.0 | https://github.com/microsoft/TypeScript.git |
|
|
49
49
|
| `@typescript/typescript-win32-x64` | 7.0.2 | Apache-2.0 | https://github.com/microsoft/TypeScript.git |
|
|
50
|
-
| `@webgpu/types` | 0.1.
|
|
51
|
-
| `bun-types` | 1.
|
|
50
|
+
| `@webgpu/types` | 0.1.72 | BSD-3-Clause | gpuweb/types |
|
|
51
|
+
| `bun-types` | 1.4.0 | MIT | https://github.com/oven-sh/bun |
|
|
52
52
|
| `prettier` | 3.9.6 | MIT | prettier/prettier |
|
|
53
53
|
| `typescript` | 7.0.2 | Apache-2.0 | https://github.com/microsoft/TypeScript.git |
|
|
54
54
|
| `undici-types` | 7.16.0 | MIT | git+https://github.com/nodejs/undici.git |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AkariSub
|
|
2
|
+
* AkariSub — ASS/SSA subtitle renderer for the browser.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Renders libass subtitles with a WebAssembly worker and an optional WebGPU or
|
|
5
|
+
* WebGL2 compositor.
|
|
5
6
|
*
|
|
6
|
-
* @
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import AkariSub from '@altq/akarisub'
|
|
10
|
+
*
|
|
11
|
+
* const renderer = new AkariSub({
|
|
12
|
+
* video: document.querySelector('video')!,
|
|
13
|
+
* subUrl: '/subtitles/example.ass'
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* renderer.destroy()
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
7
20
|
*/
|
|
8
21
|
export { default } from './wrapper';
|
|
9
22
|
export { default as AkariSub } from './wrapper';
|
|
10
|
-
export type { ASSEvent, ASSStyle, AkariSubOptions, FrameTimeline, PerformanceStats, ASSEventCallback, ASSStyleCallback, PerformanceStatsCallback, ResetStatsCallback, RenderImage, RenderTimes, VideoFrameCallbackMetadata, SubtitleColorSpace, WebYCbCrColorSpace, EncryptedSubtitleContent } from './ts/types';
|
|
11
|
-
export {
|
|
23
|
+
export type { ASSEvent, ASSStyle, AkariSubOptions, CueEvent, FontBytes, FontFamilySource, FontSubsetSource, FrameTimeline, PerformanceStats, PerformanceWarning, PreloadedTrack, PreloadTrackSource, PresentVideoFrameOptions, RenderEvent, RendererChangeEvent, RendererRecoveryReason, RendererType, StreamingTrackFormat, StreamingTrackOptions, ASSEventCallback, ASSStyleCallback, PerformanceStatsCallback, ResetStatsCallback, RenderImage, RenderTimes, VideoFrameCallbackMetadata, VideoFrameLike, SubtitleColorSpace, WebYCbCrColorSpace, EncryptedSubtitleContent } from './ts/types';
|
|
24
|
+
export type { CanvasColorSpace, VideoColorProfile, VideoPrimaries, VideoTransfer } from './ts/color-space';
|
|
25
|
+
export type { ASSSection, ASSBodyEntry } from './ts/utils';
|
|
26
|
+
export { webYCbCrMap, colorMatrixConversionMap, libassYCbCrMap, computeCanvasSize, getVideoPosition, fixAlpha, parseAss, dropBlur, fixPlayRes, testImageBugs, runFeatureTests, getAlphaBug, getBitmapBug, getColorSpaceFilterUrl, getColorMatrix3, colorMatrix3ColumnMajor, getWasmUrl, getWasmGlueUrl, getMtWasmUrl, getMtWasmGlueUrl, getDefaultFontUrl, supportsWasmSimd, supportsWasmThreads, WebGPURenderer, isWebGPUSupported, WebGL2Renderer, isWebGL2Supported, VIDEO_FRAME_TIMESTAMP_SECONDS, frameTimelineFromTimestamps, frameTimelineFromVideoFrames, isVideoFrameLike, isWebCodecsVideoFrameSupported, videoFrameCallbackMetadata, videoFrameColorSpace, videoFrameMediaTime } from './wrapper';
|
|
27
|
+
export { collectUnicodeScripts, collectUnicodeScriptsFromAss, expandScriptAlias, parseUnicodeRange, stripAssOverrides } from './ts/unicode-scripts';
|
|
28
|
+
export { collectNeededScripts, matchFontSubsets, normalizeFontFamilySource } from './ts/font-subsets';
|
|
29
|
+
export { parseStreamingTrackOptions } from './ts/streaming';
|
|
12
30
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE/C,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,WAAW,EACX,mBAAmB,EACnB,sBAAsB,EACtB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAE1G,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE1D,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,aAAa,EACb,eAAe,EACf,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,8BAA8B,EAC9B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AkariSub
|
|
2
|
+
* AkariSub — ASS/SSA subtitle renderer for the browser.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Renders libass subtitles with a WebAssembly worker and an optional WebGPU or
|
|
5
|
+
* WebGL2 compositor.
|
|
5
6
|
*
|
|
6
|
-
* @
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import AkariSub from '@altq/akarisub'
|
|
10
|
+
*
|
|
11
|
+
* const renderer = new AkariSub({
|
|
12
|
+
* video: document.querySelector('video')!,
|
|
13
|
+
* subUrl: '/subtitles/example.ass'
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* renderer.destroy()
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
7
20
|
*/
|
|
8
21
|
export { default } from './wrapper';
|
|
9
22
|
export { default as AkariSub } from './wrapper';
|
|
10
|
-
export { webYCbCrMap, colorMatrixConversionMap, libassYCbCrMap, computeCanvasSize, getVideoPosition, fixAlpha, parseAss, dropBlur, fixPlayRes, testImageBugs, runFeatureTests, getAlphaBug, getBitmapBug, getColorSpaceFilterUrl, getWasmUrl, getWasmGlueUrl, getDefaultFontUrl, WebGPURenderer, isWebGPUSupported, WebGL2Renderer, isWebGL2Supported } from './wrapper';
|
|
23
|
+
export { webYCbCrMap, colorMatrixConversionMap, libassYCbCrMap, computeCanvasSize, getVideoPosition, fixAlpha, parseAss, dropBlur, fixPlayRes, testImageBugs, runFeatureTests, getAlphaBug, getBitmapBug, getColorSpaceFilterUrl, getColorMatrix3, colorMatrix3ColumnMajor, getWasmUrl, getWasmGlueUrl, getMtWasmUrl, getMtWasmGlueUrl, getDefaultFontUrl, supportsWasmSimd, supportsWasmThreads, WebGPURenderer, isWebGPUSupported, WebGL2Renderer, isWebGL2Supported, VIDEO_FRAME_TIMESTAMP_SECONDS, frameTimelineFromTimestamps, frameTimelineFromVideoFrames, isVideoFrameLike, isWebCodecsVideoFrameSupported, videoFrameCallbackMetadata, videoFrameColorSpace, videoFrameMediaTime } from './wrapper';
|
|
24
|
+
export { collectUnicodeScripts, collectUnicodeScriptsFromAss, expandScriptAlias, parseUnicodeRange, stripAssOverrides } from './ts/unicode-scripts';
|
|
25
|
+
export { collectNeededScripts, matchFontSubsets, normalizeFontFamilySource } from './ts/font-subsets';
|
|
26
|
+
export { parseStreamingTrackOptions } from './ts/streaming';
|
|
11
27
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAA;AAsC/C,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,aAAa,EACb,eAAe,EACf,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,8BAA8B,EAC9B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAA"}
|