akarisub 0.2.1 → 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.
- package/LICENSE +3 -0
- package/README.md +226 -193
- package/THIRD_PARTY_NOTICES.md +1482 -0
- package/dist/COPYRIGHT +1480 -949
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -147
- package/dist/index.js.map +1 -0
- package/dist/ts/{ts/akarisub.d.ts → akarisub.d.ts} +80 -103
- package/dist/ts/akarisub.d.ts.map +1 -0
- package/dist/ts/akarisub.js +2086 -0
- package/dist/ts/akarisub.js.map +1 -0
- package/dist/ts/timing.d.ts +63 -0
- package/dist/ts/timing.d.ts.map +1 -0
- package/dist/ts/timing.js +219 -0
- package/dist/ts/timing.js.map +1 -0
- package/dist/ts/{ts/types.d.ts → types.d.ts} +105 -80
- package/dist/ts/types.d.ts.map +1 -0
- package/dist/ts/types.js +2 -0
- package/dist/ts/{ts/types.js.map → types.js.map} +1 -1
- package/dist/ts/{ts/utils.d.ts → utils.d.ts} +0 -31
- package/dist/ts/utils.d.ts.map +1 -0
- package/dist/ts/{ts/utils.js → utils.js} +0 -56
- package/dist/ts/utils.js.map +1 -0
- package/dist/ts/wasm.d.ts +12 -0
- package/dist/ts/wasm.d.ts.map +1 -0
- package/dist/ts/wasm.js +46 -0
- package/dist/ts/wasm.js.map +1 -0
- package/dist/ts/{ts/webgl2-renderer.d.ts → webgl2-renderer.d.ts} +2 -13
- package/dist/ts/webgl2-renderer.d.ts.map +1 -0
- package/dist/ts/{ts/webgl2-renderer.js → webgl2-renderer.js} +18 -42
- package/dist/ts/webgl2-renderer.js.map +1 -0
- package/dist/ts/{ts/webgpu-renderer.d.ts → webgpu-renderer.d.ts} +19 -20
- package/dist/ts/webgpu-renderer.d.ts.map +1 -0
- package/dist/ts/{ts/webgpu-renderer.js → webgpu-renderer.js} +164 -120
- package/dist/ts/webgpu-renderer.js.map +1 -0
- package/dist/ts/worker.d.ts +2 -0
- package/dist/ts/{ts/worker.d.ts.map → worker.d.ts.map} +1 -1
- package/dist/ts/{ts/worker.js → worker.js} +1182 -315
- package/dist/ts/worker.js.map +1 -0
- package/dist/wrapper.d.ts +8 -0
- package/dist/wrapper.d.ts.map +1 -0
- package/dist/{ts/index.js → wrapper.js} +4 -14
- package/dist/wrapper.js.map +1 -0
- package/package.json +25 -21
- package/pkg/akarisub.js +53 -0
- package/pkg/akarisub.wasm +0 -0
- package/src/wrapper.ts +46 -0
- package/dist/akarisub-worker.js +0 -39
- package/dist/akarisub-worker.wasm +0 -0
- package/dist/akarisub.umd.js +0 -159
- package/dist/ts/index.d.ts +0 -14
- package/dist/ts/index.d.ts.map +0 -1
- package/dist/ts/index.js.map +0 -1
- package/dist/ts/ts/akarisub.d.ts.map +0 -1
- package/dist/ts/ts/akarisub.js +0 -1079
- package/dist/ts/ts/akarisub.js.map +0 -1
- package/dist/ts/ts/types.d.ts.map +0 -1
- package/dist/ts/ts/types.js +0 -5
- package/dist/ts/ts/utils.d.ts.map +0 -1
- package/dist/ts/ts/utils.js.map +0 -1
- package/dist/ts/ts/webgl2-renderer.d.ts.map +0 -1
- package/dist/ts/ts/webgl2-renderer.js.map +0 -1
- package/dist/ts/ts/webgpu-renderer.d.ts.map +0 -1
- package/dist/ts/ts/webgpu-renderer.js.map +0 -1
- package/dist/ts/ts/worker.d.ts +0 -6
- package/dist/ts/ts/worker.js.map +0 -1
- package/dist/ts/wrapper.d.ts +0 -8
- package/dist/ts/wrapper.d.ts.map +0 -1
- package/dist/ts/wrapper.js +0 -9
- package/dist/ts/wrapper.js.map +0 -1
- package/src/ts/akarisub.ts +0 -1278
- package/src/ts/types.ts +0 -396
- package/src/ts/utils.ts +0 -512
- package/src/ts/webgl2-renderer.ts +0 -420
- package/src/ts/webgpu-renderer.ts +0 -736
- package/src/ts/worker.ts +0 -1973
- /package/{dist → assets}/default.woff2 +0 -0
package/LICENSE
CHANGED
|
@@ -21,3 +21,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
21
21
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
22
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
23
|
SOFTWARE.
|
|
24
|
+
|
|
25
|
+
Third-party components and bundled assets are licensed separately. See
|
|
26
|
+
THIRD_PARTY_NOTICES.md, also shipped as dist/COPYRIGHT, for dependency notices.
|
package/README.md
CHANGED
|
@@ -25,9 +25,10 @@ AkariSub is a JS wrapper for <a href="https://github.com/libass/libass">libass</
|
|
|
25
25
|
|
|
26
26
|
### Fork Enhancements
|
|
27
27
|
|
|
28
|
-
- **
|
|
28
|
+
- **GPU Rendering** - Hardware-accelerated rendering with an automatic fallback chain: WebGPU [(on browsers which support it)](https://caniuse.com/webgpu) → WebGL2 → Canvas2D
|
|
29
29
|
- **Hyper Optimizations** - Performance improvements and intelligent caching for smoother playback
|
|
30
30
|
- **Proper Fontconfig Implementation** - add Fontconfig support with multiple fallback fonts supported
|
|
31
|
+
- **Encrypted Subtitles** - optionally load AES-GCM encrypted subtitle payloads that are decrypted inside the worker, so plaintext never touches the main thread
|
|
31
32
|
- **Statistics Reporting** - Built-in statistics and performance metrics for debugging and monitoring
|
|
32
33
|
- **TypeScript Support** - Full TypeScript definitions and type safety
|
|
33
34
|
- **Updated Dependencies** - All dependencies updated to their latest versions, including libass
|
|
@@ -48,10 +49,10 @@ deno add jsr:@altq/akarisub
|
|
|
48
49
|
|
|
49
50
|
## Usage
|
|
50
51
|
|
|
51
|
-
|
|
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.
|
|
52
53
|
|
|
53
54
|
```js
|
|
54
|
-
import AkariSub from '
|
|
55
|
+
import AkariSub from 'akarisub'
|
|
55
56
|
|
|
56
57
|
const renderer = new AkariSub({
|
|
57
58
|
video: document.querySelector('video'),
|
|
@@ -59,45 +60,70 @@ const renderer = new AkariSub({
|
|
|
59
60
|
})
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
|
|
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/`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
mkdir -p public/akarisub
|
|
67
|
+
cp node_modules/akarisub/pkg/akarisub.wasm node_modules/akarisub/pkg/akarisub.js public/akarisub/
|
|
68
|
+
```
|
|
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
|
+
|
|
72
|
+
## Using only with canvas
|
|
63
73
|
|
|
64
|
-
|
|
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:
|
|
65
75
|
|
|
66
76
|
```js
|
|
67
77
|
import AkariSub from 'akarisub'
|
|
68
|
-
import workerUrl from 'akarisub/worker?url'
|
|
69
|
-
import wasmUrl from 'akarisub/worker.wasm?url'
|
|
70
78
|
|
|
71
79
|
const renderer = new AkariSub({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
wasmUrl
|
|
80
|
+
canvas: document.querySelector('canvas'),
|
|
81
|
+
subUrl: './tracks/sub.ass',
|
|
82
|
+
onDemandRender: false
|
|
76
83
|
})
|
|
84
|
+
|
|
85
|
+
// setCurrentTime(isPaused?, currentTime?, rate?)
|
|
86
|
+
renderer.setCurrentTime(true, 15)
|
|
77
87
|
```
|
|
78
88
|
|
|
79
|
-
|
|
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.
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
### Timing semantics
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
85
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])
|
|
86
101
|
const renderer = new AkariSub({
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
video,
|
|
103
|
+
subContent,
|
|
104
|
+
frameTimeline,
|
|
105
|
+
framePrefetch: 2
|
|
89
106
|
})
|
|
90
107
|
|
|
91
|
-
renderer.
|
|
108
|
+
// Timelines can also arrive after renderer construction.
|
|
109
|
+
renderer.setFrameTimeline(frameTimeline)
|
|
110
|
+
renderer.setFrameTimeline(null) // return to adaptive continuous-time rendering
|
|
92
111
|
```
|
|
93
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
|
+
|
|
94
119
|
## Changing subtitles
|
|
95
120
|
|
|
96
|
-
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
|
|
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:
|
|
97
122
|
|
|
98
123
|
- `setTrackByUrl(url):` works the same as the `subUrl` option. It will set the subtitle to display by its URL.
|
|
99
|
-
- `setTrack(content):` works the same as the `subContent` option. It will set the subtitle to display by its content.
|
|
100
|
-
- `
|
|
124
|
+
- `setTrack(content):` works the same as the `subContent` option. It will set the subtitle to display by its content (string, `Uint8Array` or `ArrayBuffer`).
|
|
125
|
+
- `setEncryptedTrack(content):` works the same as the `encryptedSubContent` option. The payload is decrypted inside the worker, so plaintext subtitles are never materialized on the main thread.
|
|
126
|
+
- `freeTrack():` this simply removes the subtitles. You can use the methods above to set a new subtitle file to be displayed.
|
|
101
127
|
|
|
102
128
|
```js
|
|
103
129
|
renderer.setTrackByUrl('/newsub.ass')
|
|
@@ -129,6 +155,7 @@ console.log(stats)
|
|
|
129
155
|
// maxRenderTime: 8.32,
|
|
130
156
|
// minRenderTime: 0.12,
|
|
131
157
|
// lastRenderTime: 1.23,
|
|
158
|
+
// timingCompensationMs: 4.8,
|
|
132
159
|
// renderFps: 60,
|
|
133
160
|
// usingWorker: true,
|
|
134
161
|
// offscreenRender: true,
|
|
@@ -151,26 +178,27 @@ console.log(`Events: ${eventCount}, Styles: ${styleCount}`)
|
|
|
151
178
|
|
|
152
179
|
**Stats Reference:**
|
|
153
180
|
|
|
154
|
-
| Property
|
|
155
|
-
|
|
156
|
-
| `framesRendered`
|
|
157
|
-
| `framesDropped`
|
|
158
|
-
| `avgRenderTime`
|
|
159
|
-
| `maxRenderTime`
|
|
160
|
-
| `minRenderTime`
|
|
161
|
-
| `lastRenderTime`
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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) |
|
|
198
|
+
|
|
199
|
+
## GPU Rendering
|
|
200
|
+
|
|
201
|
+
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:
|
|
174
202
|
|
|
175
203
|
```typescript
|
|
176
204
|
import AkariSub from 'akarisub'
|
|
@@ -178,14 +206,14 @@ import AkariSub from 'akarisub'
|
|
|
178
206
|
const renderer = new AkariSub({
|
|
179
207
|
video: document.querySelector('video'),
|
|
180
208
|
subUrl: './tracks/sub.ass',
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
console.log('WebGPU unavailable, using Canvas2D fallback')
|
|
209
|
+
onCanvasFallback: () => {
|
|
210
|
+
console.log('No GPU renderer available, using Canvas2D fallback')
|
|
184
211
|
}
|
|
185
212
|
})
|
|
186
213
|
|
|
187
|
-
//
|
|
188
|
-
|
|
214
|
+
console.log(renderer.rendererType) // 'webgpu' | 'webgl2' | 'canvas2d'
|
|
215
|
+
|
|
216
|
+
if (renderer.isUsingGPURenderer) {
|
|
189
217
|
console.log('GPU-accelerated rendering enabled!')
|
|
190
218
|
}
|
|
191
219
|
```
|
|
@@ -194,178 +222,190 @@ if (renderer.isUsingWebGPU) {
|
|
|
194
222
|
|
|
195
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.
|
|
196
224
|
|
|
197
|
-
| Option
|
|
198
|
-
|
|
199
|
-
| `video`
|
|
200
|
-
| `canvas`
|
|
201
|
-
| `blendMode`
|
|
202
|
-
| `asyncRender`
|
|
203
|
-
| `offscreenRender`
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
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) |
|
|
226
261
|
|
|
227
262
|
## Methods
|
|
228
263
|
|
|
229
264
|
### Track Management
|
|
230
265
|
|
|
231
|
-
| Method
|
|
232
|
-
|
|
233
|
-
| `setTrackByUrl(url)`
|
|
234
|
-
| `setTrack(content)`
|
|
235
|
-
| `
|
|
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 |
|
|
236
272
|
|
|
237
273
|
### Playback Control
|
|
238
274
|
|
|
239
|
-
| Method
|
|
240
|
-
|
|
241
|
-
| `setIsPaused(isPaused)`
|
|
242
|
-
| `setRate(rate)`
|
|
243
|
-
| `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 |
|
|
244
281
|
|
|
245
282
|
### Video & Canvas
|
|
246
283
|
|
|
247
|
-
| Method
|
|
248
|
-
|
|
249
|
-
| `setVideo(video)`
|
|
250
|
-
| `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 |
|
|
251
288
|
|
|
252
289
|
### Event Management
|
|
253
290
|
|
|
254
|
-
| Method
|
|
255
|
-
|
|
256
|
-
| `createEvent(event)`
|
|
257
|
-
| `setEvent(event, index)` | `event: Partial<ASSEvent>, index: number` | `void`
|
|
258
|
-
| `removeEvent(index)`
|
|
259
|
-
| `getEvents()`
|
|
260
|
-
| `getEventCount()`
|
|
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) |
|
|
261
298
|
|
|
262
299
|
### Style Management
|
|
263
300
|
|
|
264
|
-
| Method
|
|
265
|
-
|
|
266
|
-
| `createStyle(style)`
|
|
267
|
-
| `setStyle(style, index)` | `style: Partial<ASSStyle>, index: number` | `void`
|
|
268
|
-
| `removeStyle(index)`
|
|
269
|
-
| `getStyles()`
|
|
270
|
-
| `getStyleCount()`
|
|
271
|
-
| `styleOverride(style)`
|
|
272
|
-
| `disableStyleOverride()` | -
|
|
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 |
|
|
273
310
|
|
|
274
311
|
### Font Management
|
|
275
312
|
|
|
276
|
-
| Method
|
|
277
|
-
|
|
278
|
-
| `addFont(font)`
|
|
279
|
-
| `setDefaultFont(font)` | `font: string`
|
|
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 |
|
|
280
317
|
|
|
281
318
|
### Statistics & Debugging
|
|
282
319
|
|
|
283
|
-
| Method
|
|
284
|
-
|
|
285
|
-
| `getStats()`
|
|
286
|
-
| `resetStats()`
|
|
287
|
-
| `getEventCount()` | -
|
|
288
|
-
| `getStyleCount()` | -
|
|
289
|
-
| `runBenchmark()` | - | `void` | Run a benchmark on the worker |
|
|
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) |
|
|
290
326
|
|
|
291
327
|
### Lifecycle
|
|
292
328
|
|
|
293
|
-
| Method
|
|
294
|
-
|
|
295
|
-
| `destroy(err?)`
|
|
296
|
-
| `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 |
|
|
297
333
|
|
|
298
334
|
## Properties
|
|
299
335
|
|
|
300
|
-
| Property
|
|
301
|
-
|
|
302
|
-
| `debug`
|
|
303
|
-
| `prescaleFactor`
|
|
304
|
-
| `prescaleHeightLimit` | number
|
|
305
|
-
| `maxRenderHeight`
|
|
306
|
-
| `timeOffset`
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
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'` |
|
|
309
349
|
|
|
310
350
|
## Type Definitions
|
|
311
351
|
|
|
312
352
|
### ASSEvent
|
|
313
353
|
|
|
314
|
-
| Property
|
|
315
|
-
|
|
316
|
-
| `Start`
|
|
317
|
-
| `Duration`
|
|
318
|
-
| `Style`
|
|
319
|
-
| `Name`
|
|
320
|
-
| `MarginL`
|
|
321
|
-
| `MarginR`
|
|
322
|
-
| `MarginV`
|
|
323
|
-
| `Effect`
|
|
324
|
-
| `Text`
|
|
325
|
-
| `ReadOrder` | number | Read order number
|
|
326
|
-
| `Layer`
|
|
327
|
-
| `_index`
|
|
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) |
|
|
328
368
|
|
|
329
369
|
### ASSStyle
|
|
330
370
|
|
|
331
|
-
| Property
|
|
332
|
-
|
|
333
|
-
| `Name`
|
|
334
|
-
| `FontName`
|
|
335
|
-
| `FontSize`
|
|
336
|
-
| `PrimaryColour`
|
|
337
|
-
| `SecondaryColour`
|
|
338
|
-
| `OutlineColour`
|
|
339
|
-
| `BackColour`
|
|
340
|
-
| `Bold`
|
|
341
|
-
| `Italic`
|
|
342
|
-
| `Underline`
|
|
343
|
-
| `StrikeOut`
|
|
344
|
-
| `ScaleX`
|
|
345
|
-
| `ScaleY`
|
|
346
|
-
| `Spacing`
|
|
347
|
-
| `Angle`
|
|
348
|
-
| `BorderStyle`
|
|
349
|
-
| `Outline`
|
|
350
|
-
| `Shadow`
|
|
351
|
-
| `Alignment`
|
|
352
|
-
| `MarginL`
|
|
353
|
-
| `MarginR`
|
|
354
|
-
| `MarginV`
|
|
355
|
-
| `Encoding`
|
|
356
|
-
| `treat_fontname_as_pattern` | number | Treat font name as pattern
|
|
357
|
-
| `Blur`
|
|
358
|
-
| `Justify`
|
|
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 |
|
|
359
399
|
|
|
360
400
|
# How to build?
|
|
361
401
|
|
|
362
402
|
## Dependencies
|
|
363
403
|
|
|
404
|
+
[mise](https://mise.jdx.dev) manages the toolchain (emsdk, bun, cmake — see `mise.toml`). You additionally need the usual autotools build dependencies:
|
|
405
|
+
|
|
364
406
|
- git
|
|
365
|
-
- emscripten (Configure the enviroment)
|
|
366
407
|
- make
|
|
367
408
|
- python3
|
|
368
|
-
- cmake
|
|
369
409
|
- pkgconfig
|
|
370
410
|
- patch
|
|
371
411
|
- libtool
|
|
@@ -374,29 +414,22 @@ The default options are best, and automatically fallback to the next fastest opt
|
|
|
374
414
|
- ragel - Required by Harfbuzz
|
|
375
415
|
- itstool - Required by Fontconfig
|
|
376
416
|
- gperf - Required by Fontconfig
|
|
377
|
-
- licensecheck
|
|
378
417
|
|
|
379
418
|
## Get the Source
|
|
380
419
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
### Docker
|
|
386
|
-
|
|
387
|
-
1. Install Docker
|
|
388
|
-
2. ./run-docker-build.sh
|
|
389
|
-
3. Artifacts are in /dist/js
|
|
390
|
-
|
|
391
|
-
### Buildah
|
|
420
|
+
```bash
|
|
421
|
+
git clone --recursive https://github.com/altqx/akarisub.git
|
|
422
|
+
```
|
|
392
423
|
|
|
393
|
-
|
|
394
|
-
2. ./run-buildah-build.sh
|
|
395
|
-
3. Artifacts are in /dist/js
|
|
424
|
+
## Build
|
|
396
425
|
|
|
397
|
-
|
|
426
|
+
```bash
|
|
427
|
+
mise install # installs emsdk, bun, cmake
|
|
428
|
+
bun install # JS dependencies
|
|
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
|
|
431
|
+
```
|
|
398
432
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
3. Artifacts are in /dist/js
|
|
433
|
+
- If on macOS with libtool from brew, `LIBTOOLIZE=glibtoolize make`
|
|
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)
|