libbitsub 1.6.0 → 1.7.1
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 +407 -378
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/ts/parsers.d.ts +18 -1
- package/dist/ts/parsers.d.ts.map +1 -1
- package/dist/ts/parsers.js +151 -0
- package/dist/ts/parsers.js.map +1 -1
- package/dist/ts/renderers.d.ts +47 -4
- package/dist/ts/renderers.d.ts.map +1 -1
- package/dist/ts/renderers.js +391 -87
- package/dist/ts/renderers.js.map +1 -1
- package/dist/ts/types.d.ts +144 -0
- package/dist/ts/types.d.ts.map +1 -1
- package/dist/ts/utils.d.ts +11 -1
- package/dist/ts/utils.d.ts.map +1 -1
- package/dist/ts/utils.js +100 -1
- package/dist/ts/utils.js.map +1 -1
- package/dist/ts/wasm.js.map +1 -1
- package/dist/ts/webgl2-renderer.d.ts +2 -1
- package/dist/ts/webgl2-renderer.d.ts.map +1 -1
- package/dist/ts/webgl2-renderer.js +10 -7
- package/dist/ts/webgl2-renderer.js.map +1 -1
- package/dist/ts/webgpu-renderer.d.ts +4 -1
- package/dist/ts/webgpu-renderer.d.ts.map +1 -1
- package/dist/ts/webgpu-renderer.js +82 -32
- package/dist/ts/webgpu-renderer.js.map +1 -1
- package/dist/ts/worker.d.ts.map +1 -1
- package/dist/ts/worker.js +145 -87
- package/dist/ts/worker.js.map +1 -1
- package/dist/wrapper.d.ts +3 -2
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +3 -1
- package/dist/wrapper.js.map +1 -1
- package/package.json +3 -2
- package/pkg/README.md +407 -378
- package/pkg/libbitsub.d.ts +121 -1
- package/pkg/libbitsub.js +251 -15
- package/pkg/libbitsub_bg.wasm +0 -0
- package/pkg/libbitsub_bg.wasm.d.ts +25 -1
- package/pkg/package.json +1 -1
- package/src/wrapper.ts +14 -1
package/README.md
CHANGED
|
@@ -2,33 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
High-performance WASM renderer for graphical subtitles (PGS and VobSub), written in Rust.
|
|
4
4
|
|
|
5
|
-
Started as a fork of Arcus92's [libpgs-js](https://github.com/Arcus92/libpgs-js), this project
|
|
5
|
+
Started as a fork of Arcus92's [libpgs-js](https://github.com/Arcus92/libpgs-js), this project was reworked for higher performance and broader format support. It keeps the familiar high-level PGS-oriented API while adding a lower-level parser surface, VobSub support, GPU backends, and worker-backed rendering.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- PGS (Blu-ray) subtitle parsing and rendering
|
|
10
|
+
- VobSub (DVD) subtitle parsing and rendering
|
|
11
|
+
- WebGPU, WebGL2, and Canvas2D rendering with automatic fallback
|
|
12
|
+
- Worker-backed parsing/rendering for large subtitle files
|
|
13
|
+
- Rich layout controls: scale, horizontal/vertical offsets, alignment, bottom padding, safe area, opacity
|
|
14
|
+
- Cue metadata and parser introspection APIs
|
|
15
|
+
- Frame prefetching and cache control for high-level renderers
|
|
16
|
+
- Automatic format detection and unified loading helpers
|
|
17
|
+
- TypeScript support with exported event and metadata types
|
|
16
18
|
|
|
17
19
|
## Showcase
|
|
18
20
|
|
|
19
|
-
### PGS
|
|
21
|
+
### PGS
|
|
20
22
|
|
|
21
23
|
https://gist.github.com/user-attachments/assets/55ac8e11-1964-4fb9-923e-dcac82dc7703
|
|
22
24
|
|
|
23
|
-
###
|
|
25
|
+
### VobSub
|
|
24
26
|
|
|
25
27
|
https://gist.github.com/user-attachments/assets/a89ae9fe-23e4-4bc3-8cad-16a3f0fea665
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### Live demo
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
https://a.rafasu.com/v
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
## Installation
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
36
|
npm install libbitsub
|
|
@@ -36,544 +38,571 @@ npm install libbitsub
|
|
|
36
38
|
bun add libbitsub
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
For JSR:
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
deno add jsr:@altq/libbitsub
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
## Worker setup
|
|
46
48
|
|
|
47
|
-
For best performance
|
|
49
|
+
For best performance, make the generated WASM assets reachable by the browser so the shared worker can load them:
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
|
-
# For Next.js, Vite, or similar frameworks
|
|
51
52
|
mkdir -p public/libbitsub
|
|
52
53
|
cp node_modules/libbitsub/pkg/libbitsub_bg.wasm public/libbitsub/
|
|
53
54
|
cp node_modules/libbitsub/pkg/libbitsub.js public/libbitsub/
|
|
54
55
|
```
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
`workerUrl` still exists in the option type for compatibility, but the current implementation creates an inline shared worker and resolves the WASM asset from the package loader. Supplying `workerUrl` does not change runtime behavior.
|
|
57
58
|
|
|
58
|
-
##
|
|
59
|
+
## Building from source
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
Prerequisites:
|
|
61
62
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
63
|
+
- Rust
|
|
64
|
+
- wasm-pack
|
|
65
|
+
- Bun
|
|
64
66
|
|
|
65
67
|
```bash
|
|
66
|
-
# Install wasm-pack
|
|
67
68
|
cargo install wasm-pack
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Building
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
# Build WASM module and TypeScript wrapper
|
|
74
69
|
bun run build
|
|
75
|
-
|
|
76
|
-
# Build WASM only (for development)
|
|
77
|
-
bun run build:wasm
|
|
78
|
-
|
|
79
|
-
# Build release version (optimized)
|
|
80
|
-
bun run build:wasm:release
|
|
81
70
|
```
|
|
82
71
|
|
|
83
|
-
##
|
|
72
|
+
## Quick start
|
|
84
73
|
|
|
85
|
-
|
|
74
|
+
Initialize the WASM module once before using any parser or renderer:
|
|
86
75
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
76
|
+
```ts
|
|
90
77
|
import { initWasm } from 'libbitsub'
|
|
91
78
|
|
|
92
|
-
// Initialize WASM (do this once at app startup)
|
|
93
79
|
await initWasm()
|
|
94
80
|
```
|
|
95
81
|
|
|
96
|
-
## High-
|
|
82
|
+
## High-level video renderers
|
|
97
83
|
|
|
98
|
-
The high-level API
|
|
84
|
+
The high-level API manages subtitle loading, canvas overlay creation, playback sync, resize handling, worker usage, and renderer fallback.
|
|
99
85
|
|
|
100
|
-
### PGS
|
|
86
|
+
### PGS renderer
|
|
101
87
|
|
|
102
|
-
```
|
|
88
|
+
```ts
|
|
103
89
|
import { PgsRenderer } from 'libbitsub'
|
|
104
90
|
|
|
105
|
-
// Create renderer with video element (URL-based loading)
|
|
106
91
|
const renderer = new PgsRenderer({
|
|
107
92
|
video: videoElement,
|
|
108
93
|
subUrl: '/subtitles/movie.sup',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
video: videoElement,
|
|
122
|
-
subContent: subtitleData, // Load directly from ArrayBuffer
|
|
123
|
-
onLoading: () => console.log('Loading subtitles...'),
|
|
124
|
-
onLoaded: () => console.log('Subtitles loaded!'),
|
|
125
|
-
onError: (error) => console.error('Failed to load:', error)
|
|
94
|
+
displaySettings: {
|
|
95
|
+
scale: 1.1,
|
|
96
|
+
bottomPadding: 4,
|
|
97
|
+
safeArea: 5
|
|
98
|
+
},
|
|
99
|
+
cacheLimit: 32,
|
|
100
|
+
prefetchWindow: { before: 1, after: 2 },
|
|
101
|
+
onEvent: (event) => {
|
|
102
|
+
if (event.type === 'worker-state') {
|
|
103
|
+
console.log('worker', event.ready ? 'ready' : 'starting', event.sessionId)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
126
106
|
})
|
|
127
107
|
|
|
128
|
-
// The renderer automatically:
|
|
129
|
-
// - Fetches the subtitle file (if using subUrl) or uses provided ArrayBuffer
|
|
130
|
-
// - Creates a canvas overlay on the video
|
|
131
|
-
// - Syncs rendering with video playback
|
|
132
|
-
// - Handles resize events
|
|
133
|
-
|
|
134
|
-
// When done:
|
|
135
108
|
renderer.dispose()
|
|
136
109
|
```
|
|
137
110
|
|
|
138
|
-
### VobSub
|
|
111
|
+
### VobSub renderer
|
|
139
112
|
|
|
140
|
-
```
|
|
113
|
+
```ts
|
|
141
114
|
import { VobSubRenderer } from 'libbitsub'
|
|
142
115
|
|
|
143
|
-
// Create renderer with video element (URL-based loading)
|
|
144
116
|
const renderer = new VobSubRenderer({
|
|
145
117
|
video: videoElement,
|
|
146
118
|
subUrl: '/subtitles/movie.sub',
|
|
147
|
-
idxUrl: '/subtitles/movie.idx'
|
|
148
|
-
workerUrl: '/libbitsub.js', // Optional
|
|
149
|
-
// Lifecycle callbacks (optional)
|
|
150
|
-
onLoading: () => setIsLoading(true),
|
|
151
|
-
onLoaded: () => setIsLoading(false),
|
|
152
|
-
onError: (error) => {
|
|
153
|
-
setIsLoading(false)
|
|
154
|
-
console.error('Subtitle error:', error)
|
|
155
|
-
}
|
|
119
|
+
idxUrl: '/subtitles/movie.idx'
|
|
156
120
|
})
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const idxData = await idxResponse.text()
|
|
122
|
+
renderer.setDebandThreshold(64)
|
|
123
|
+
renderer.setDebandRange(15)
|
|
124
|
+
```
|
|
162
125
|
|
|
163
|
-
|
|
126
|
+
### Automatic format detection
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { createAutoSubtitleRenderer } from 'libbitsub'
|
|
130
|
+
|
|
131
|
+
const renderer = createAutoSubtitleRenderer({
|
|
164
132
|
video: videoElement,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
onLoading: () => setIsLoading(true),
|
|
168
|
-
onLoaded: () => setIsLoading(false),
|
|
169
|
-
onError: (error) => {
|
|
170
|
-
setIsLoading(false)
|
|
171
|
-
console.error('Subtitle error:', error)
|
|
172
|
-
}
|
|
133
|
+
subUrl: '/subtitles/track.sup',
|
|
134
|
+
fileName: 'track.sup'
|
|
173
135
|
})
|
|
174
|
-
|
|
175
|
-
// When done:
|
|
176
|
-
renderer.dispose()
|
|
177
136
|
```
|
|
178
137
|
|
|
179
|
-
|
|
138
|
+
Automatic detection uses file hints when available and otherwise inspects the binary payload. If the format cannot be identified confidently, it throws instead of silently forcing a parser.
|
|
180
139
|
|
|
181
|
-
|
|
140
|
+
## Layout controls
|
|
182
141
|
|
|
183
|
-
|
|
184
|
-
// Get current settings
|
|
185
|
-
const settings = renderer.getDisplaySettings()
|
|
186
|
-
console.log(settings)
|
|
187
|
-
// Output: { scale: 1.0, verticalOffset: 0 }
|
|
142
|
+
Both `PgsRenderer` and `VobSubRenderer` support runtime layout changes:
|
|
188
143
|
|
|
189
|
-
|
|
144
|
+
```ts
|
|
190
145
|
renderer.setDisplaySettings({
|
|
191
|
-
scale: 1.2,
|
|
192
|
-
verticalOffset: -
|
|
146
|
+
scale: 1.2,
|
|
147
|
+
verticalOffset: -8,
|
|
148
|
+
horizontalOffset: 2,
|
|
149
|
+
horizontalAlign: 'center',
|
|
150
|
+
bottomPadding: 6,
|
|
151
|
+
safeArea: 5,
|
|
152
|
+
opacity: 0.92
|
|
193
153
|
})
|
|
194
154
|
|
|
195
|
-
|
|
155
|
+
const settings = renderer.getDisplaySettings()
|
|
196
156
|
renderer.resetDisplaySettings()
|
|
197
157
|
```
|
|
198
158
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
VobSub subtitles often exhibit banding artifacts due to their limited 4-color palette. libbitsub includes a neo_f3kdb-style debanding filter that smooths color transitions:
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
import { VobSubRenderer } from 'libbitsub'
|
|
159
|
+
`SubtitleDisplaySettings`:
|
|
205
160
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
161
|
+
| Field | Type | Range / values | Meaning |
|
|
162
|
+
| --- | --- | --- | --- |
|
|
163
|
+
| `scale` | number | `0.1` to `3.0` | Overall subtitle scale |
|
|
164
|
+
| `verticalOffset` | number | `-50` to `50` | Vertical movement as percent of video height |
|
|
165
|
+
| `horizontalOffset` | number | `-50` to `50` | Horizontal movement as percent of video width |
|
|
166
|
+
| `horizontalAlign` | `'left' \| 'center' \| 'right'` | fixed set | Anchor used when scaling subtitle groups |
|
|
167
|
+
| `bottomPadding` | number | `0` to `50` | Extra padding from the bottom edge |
|
|
168
|
+
| `safeArea` | number | `0` to `25` | Clamp subtitles inside a video-safe area |
|
|
169
|
+
| `opacity` | number | `0.0` to `1.0` | Global subtitle opacity |
|
|
210
170
|
|
|
211
|
-
|
|
212
|
-
// renderer.setDebandEnabled(false)
|
|
171
|
+
## Metadata and introspection
|
|
213
172
|
|
|
214
|
-
|
|
215
|
-
renderer.setDebandThreshold(64.0) // Higher = more aggressive smoothing
|
|
216
|
-
renderer.setDebandRange(15) // Pixel radius for sampling
|
|
173
|
+
High-level renderers expose parser and cue metadata:
|
|
217
174
|
|
|
218
|
-
|
|
219
|
-
|
|
175
|
+
```ts
|
|
176
|
+
const metadata = renderer.getMetadata()
|
|
177
|
+
const currentCue = renderer.getCurrentCueMetadata()
|
|
178
|
+
const cue42 = renderer.getCueMetadata(42)
|
|
220
179
|
```
|
|
221
180
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
import { VobSubParserLowLevel } from 'libbitsub'
|
|
181
|
+
Low-level parsers expose the same model:
|
|
226
182
|
|
|
227
|
-
|
|
228
|
-
|
|
183
|
+
```ts
|
|
184
|
+
import { PgsParser, UnifiedSubtitleParser, VobSubParserLowLevel } from 'libbitsub'
|
|
229
185
|
|
|
230
|
-
|
|
231
|
-
parser.
|
|
232
|
-
parser.setDebandThreshold(48.0)
|
|
233
|
-
parser.setDebandRange(12)
|
|
186
|
+
const parser = new UnifiedSubtitleParser()
|
|
187
|
+
const detected = parser.loadAuto({ data: subtitleBytes, fileName: 'track.sup' })
|
|
234
188
|
|
|
235
|
-
|
|
236
|
-
|
|
189
|
+
console.log(detected)
|
|
190
|
+
console.log(parser.getMetadata())
|
|
191
|
+
console.log(parser.getCueMetadata(0))
|
|
237
192
|
```
|
|
238
193
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
| Property | Type | Default | Range | Description |
|
|
242
|
-
| ----------- | ------- | ------- | --------- | ------------------------------------------------ |
|
|
243
|
-
| `enabled` | boolean | `true` | - | Enable/disable the debanding filter |
|
|
244
|
-
| `threshold` | number | `64.0` | 0.0-255.0 | Difference threshold; higher = more smoothing |
|
|
245
|
-
| `range` | number | `15` | 1-64 | Sample radius in pixels; higher = wider sampling |
|
|
194
|
+
Metadata includes:
|
|
246
195
|
|
|
247
|
-
|
|
196
|
+
- Track format, cue count, and presentation size
|
|
197
|
+
- Cue start/end time and duration
|
|
198
|
+
- Rendered cue bounds when available
|
|
199
|
+
- PGS composition count, palette ID, composition state
|
|
200
|
+
- VobSub language, track ID, IDX metadata presence, file position where available
|
|
248
201
|
|
|
249
|
-
|
|
250
|
-
- Uses cross-shaped sampling with factor-based blending (neo_f3kdb sample_mode 6 style)
|
|
251
|
-
- Transparent pixels are skipped for performance
|
|
252
|
-
- Deterministic output (same input = same output)
|
|
202
|
+
## Cache control and prefetching
|
|
253
203
|
|
|
254
|
-
|
|
204
|
+
High-level renderers expose cache helpers:
|
|
255
205
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
206
|
+
```ts
|
|
207
|
+
renderer.setCacheLimit(48)
|
|
208
|
+
await renderer.prefetchRange(10, 20)
|
|
209
|
+
await renderer.prefetchAroundTime(videoElement.currentTime)
|
|
210
|
+
renderer.clearFrameCache()
|
|
211
|
+
```
|
|
261
212
|
|
|
262
|
-
|
|
263
|
-
- `0` = Original position
|
|
264
|
-
- Negative values move up (e.g., `-10` moves up by 10% of height)
|
|
265
|
-
- Positive values move down (e.g., `10` moves down by 10% of height)
|
|
266
|
-
- Range: `-50` to `50`
|
|
213
|
+
`clearFrameCache()` clears both the renderer-side frame map and the underlying parser cache for the active session.
|
|
267
214
|
|
|
268
|
-
|
|
215
|
+
## Observability events
|
|
269
216
|
|
|
270
|
-
|
|
217
|
+
Use `onEvent` to observe renderer lifecycle and runtime behavior:
|
|
271
218
|
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// Example: Display stats in a debug overlay
|
|
293
|
-
setInterval(() => {
|
|
294
|
-
const stats = renderer.getStats()
|
|
295
|
-
debugOverlay.textContent = `
|
|
296
|
-
FPS: ${stats.renderFps}
|
|
297
|
-
Frames: ${stats.framesRendered} (dropped: ${stats.framesDropped})
|
|
298
|
-
Avg render: ${stats.avgRenderTime}ms
|
|
299
|
-
Worker: ${stats.usingWorker ? 'Yes' : 'No'}
|
|
300
|
-
Cache: ${stats.cachedFrames} frames
|
|
301
|
-
`
|
|
302
|
-
}, 1000)
|
|
219
|
+
```ts
|
|
220
|
+
const renderer = new PgsRenderer({
|
|
221
|
+
video: videoElement,
|
|
222
|
+
subUrl: '/subtitles/movie.sup',
|
|
223
|
+
onEvent: (event) => {
|
|
224
|
+
switch (event.type) {
|
|
225
|
+
case 'loading':
|
|
226
|
+
case 'loaded':
|
|
227
|
+
case 'error':
|
|
228
|
+
case 'renderer-change':
|
|
229
|
+
case 'worker-state':
|
|
230
|
+
case 'cache-change':
|
|
231
|
+
case 'cue-change':
|
|
232
|
+
case 'stats':
|
|
233
|
+
console.log(event)
|
|
234
|
+
break
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
303
238
|
```
|
|
304
239
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
| `framesRendered` | number | Total frames rendered since initialization |
|
|
310
|
-
| `framesDropped` | number | Frames dropped due to slow rendering (>16.67ms) |
|
|
311
|
-
| `avgRenderTime` | number | Average render time in milliseconds (rolling 60-sample window) |
|
|
312
|
-
| `maxRenderTime` | number | Maximum render time in milliseconds |
|
|
313
|
-
| `minRenderTime` | number | Minimum render time in milliseconds |
|
|
314
|
-
| `lastRenderTime` | number | Most recent render time in milliseconds |
|
|
315
|
-
| `renderFps` | number | Current renders per second (based on last 1 second) |
|
|
316
|
-
| `usingWorker` | boolean | Whether rendering is using Web Worker (off-main-thread) |
|
|
317
|
-
| `cachedFrames` | number | Number of decoded frames currently cached |
|
|
318
|
-
| `pendingRenders` | number | Number of frames currently being decoded asynchronously |
|
|
319
|
-
| `totalEntries` | number | Total subtitle entries/display sets in the loaded file |
|
|
320
|
-
| `currentIndex` | number | Index of the currently displayed subtitle |
|
|
321
|
-
|
|
322
|
-
### GPU-Accelerated Rendering
|
|
323
|
-
|
|
324
|
-
libbitsub automatically selects the best available GPU renderer at startup, following this fallback chain:
|
|
325
|
-
|
|
326
|
-
**WebGPU → WebGL2 → Canvas2D**
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
import { PgsRenderer, isWebGPUSupported, isWebGL2Supported } from 'libbitsub'
|
|
330
|
-
|
|
331
|
-
// Check renderer support
|
|
332
|
-
if (isWebGPUSupported()) {
|
|
333
|
-
console.log('WebGPU available')
|
|
334
|
-
} else if (isWebGL2Supported()) {
|
|
335
|
-
console.log('WebGL2 available')
|
|
336
|
-
} else {
|
|
337
|
-
console.log('Falling back to Canvas2D')
|
|
338
|
-
}
|
|
240
|
+
### Example: event-driven prefetch and cue inspection
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { PgsRenderer } from 'libbitsub'
|
|
339
244
|
|
|
340
245
|
const renderer = new PgsRenderer({
|
|
341
246
|
video: videoElement,
|
|
342
247
|
subUrl: '/subtitles/movie.sup',
|
|
343
|
-
|
|
344
|
-
|
|
248
|
+
prefetchWindow: { before: 1, after: 2 },
|
|
249
|
+
onEvent: async (event) => {
|
|
250
|
+
switch (event.type) {
|
|
251
|
+
case 'loaded': {
|
|
252
|
+
console.log('track metadata', event.metadata)
|
|
253
|
+
await renderer.prefetchAroundTime(videoElement.currentTime)
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
case 'cue-change': {
|
|
258
|
+
if (!event.cue) {
|
|
259
|
+
console.log('no active subtitle cue')
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const cue = renderer.getCueMetadata(event.cue.index)
|
|
264
|
+
console.log('active cue', {
|
|
265
|
+
index: cue?.index,
|
|
266
|
+
startTime: cue?.startTime,
|
|
267
|
+
endTime: cue?.endTime,
|
|
268
|
+
bounds: cue?.bounds,
|
|
269
|
+
compositionCount: cue?.compositionCount
|
|
270
|
+
})
|
|
271
|
+
break
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case 'cache-change': {
|
|
275
|
+
console.log('cache', `${event.cachedFrames}/${event.cacheLimit}`, 'pending', event.pendingRenders)
|
|
276
|
+
break
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
345
280
|
})
|
|
346
|
-
```
|
|
347
281
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
- `onWebGL2Fallback` (function): Callback when WebGL2 initialisation fails
|
|
282
|
+
videoElement.addEventListener('seeked', () => {
|
|
283
|
+
renderer.prefetchAroundTime(videoElement.currentTime).catch(console.error)
|
|
284
|
+
})
|
|
352
285
|
|
|
353
|
-
|
|
286
|
+
// later
|
|
287
|
+
renderer.dispose()
|
|
288
|
+
```
|
|
354
289
|
|
|
355
|
-
|
|
356
|
-
| -------- | ------------------- | --------------- | --------------- |
|
|
357
|
-
| WebGPU | ✅ | ✅ | Chrome 113+, Firefox 141+, Edge 113+ |
|
|
358
|
-
| WebGL2 | ✅ | ✅ | All modern browsers |
|
|
359
|
-
| Canvas2D | — | ✅ | Universal |
|
|
290
|
+
Emitted events:
|
|
360
291
|
|
|
361
|
-
|
|
292
|
+
| Event | Payload |
|
|
293
|
+
| --- | --- |
|
|
294
|
+
| `loading` | subtitle format |
|
|
295
|
+
| `loaded` | subtitle format and parser metadata |
|
|
296
|
+
| `error` | subtitle format and `Error` |
|
|
297
|
+
| `renderer-change` | active backend: `webgpu`, `webgl2`, or `canvas2d` |
|
|
298
|
+
| `worker-state` | whether worker mode is enabled, ready, fallback status, and the active session ID |
|
|
299
|
+
| `cache-change` | cached frame count, pending renders, and configured cache limit |
|
|
300
|
+
| `cue-change` | current cue metadata or `null` when nothing is displayed |
|
|
301
|
+
| `stats` | periodic renderer stats snapshot |
|
|
362
302
|
|
|
363
|
-
|
|
303
|
+
## Performance stats
|
|
364
304
|
|
|
365
|
-
|
|
305
|
+
```ts
|
|
306
|
+
const stats = renderer.getStats()
|
|
307
|
+
```
|
|
366
308
|
|
|
367
|
-
|
|
368
|
-
import { initWasm, PgsParser } from 'libbitsub'
|
|
309
|
+
`SubtitleRendererStats` includes:
|
|
369
310
|
|
|
370
|
-
|
|
311
|
+
- `framesRendered`
|
|
312
|
+
- `framesDropped`
|
|
313
|
+
- `avgRenderTime`
|
|
314
|
+
- `maxRenderTime`
|
|
315
|
+
- `minRenderTime`
|
|
316
|
+
- `lastRenderTime`
|
|
317
|
+
- `renderFps`
|
|
318
|
+
- `usingWorker`
|
|
319
|
+
- `cachedFrames`
|
|
320
|
+
- `pendingRenders`
|
|
321
|
+
- `totalEntries`
|
|
322
|
+
- `currentIndex`
|
|
371
323
|
|
|
372
|
-
|
|
324
|
+
## Low-level APIs
|
|
373
325
|
|
|
374
|
-
|
|
375
|
-
const response = await fetch('subtitles.sup')
|
|
376
|
-
const data = new Uint8Array(await response.arrayBuffer())
|
|
377
|
-
parser.load(data)
|
|
326
|
+
### PGS parser
|
|
378
327
|
|
|
379
|
-
|
|
380
|
-
|
|
328
|
+
```ts
|
|
329
|
+
import { PgsParser } from 'libbitsub'
|
|
381
330
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (subtitleData) {
|
|
385
|
-
for (const comp of subtitleData.compositionData) {
|
|
386
|
-
ctx.putImageData(comp.pixelData, comp.x, comp.y)
|
|
387
|
-
}
|
|
388
|
-
}
|
|
331
|
+
const parser = new PgsParser()
|
|
332
|
+
parser.load(new Uint8Array(arrayBuffer))
|
|
389
333
|
|
|
390
|
-
|
|
391
|
-
parser.
|
|
334
|
+
const timestamps = parser.getTimestamps()
|
|
335
|
+
const frame = parser.renderAtIndex(0)
|
|
336
|
+
const metadata = parser.getMetadata()
|
|
392
337
|
```
|
|
393
338
|
|
|
394
|
-
### VobSub
|
|
339
|
+
### VobSub parser
|
|
395
340
|
|
|
396
|
-
```
|
|
397
|
-
import {
|
|
398
|
-
|
|
399
|
-
await initWasm()
|
|
341
|
+
```ts
|
|
342
|
+
import { VobSubParserLowLevel } from 'libbitsub'
|
|
400
343
|
|
|
401
344
|
const parser = new VobSubParserLowLevel()
|
|
345
|
+
parser.loadFromData(idxContent, new Uint8Array(subArrayBuffer))
|
|
346
|
+
parser.setDebandEnabled(true)
|
|
402
347
|
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
const subResponse = await fetch('subtitles.sub')
|
|
407
|
-
const subData = new Uint8Array(await subResponse.arrayBuffer())
|
|
348
|
+
const frame = parser.renderAtTimestamp(120.5)
|
|
349
|
+
const cue = parser.getCueMetadata(0)
|
|
350
|
+
```
|
|
408
351
|
|
|
409
|
-
parser
|
|
352
|
+
### Unified parser
|
|
410
353
|
|
|
411
|
-
|
|
412
|
-
|
|
354
|
+
```ts
|
|
355
|
+
import { UnifiedSubtitleParser, detectSubtitleFormat } from 'libbitsub'
|
|
413
356
|
|
|
414
|
-
|
|
415
|
-
const subtitleData = parser.renderAtTimestamp(currentTimeInSeconds)
|
|
416
|
-
if (subtitleData) {
|
|
417
|
-
for (const comp of subtitleData.compositionData) {
|
|
418
|
-
ctx.putImageData(comp.pixelData, comp.x, comp.y)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
357
|
+
const format = detectSubtitleFormat({ data: subtitleBytes, fileName: 'track.sup' })
|
|
421
358
|
|
|
422
|
-
parser
|
|
359
|
+
const parser = new UnifiedSubtitleParser()
|
|
360
|
+
parser.loadAuto({ data: subtitleBytes, fileName: 'track.sup' })
|
|
423
361
|
```
|
|
424
362
|
|
|
425
|
-
|
|
363
|
+
## GPU backends
|
|
426
364
|
|
|
427
|
-
|
|
365
|
+
libbitsub prefers:
|
|
428
366
|
|
|
429
|
-
|
|
430
|
-
|
|
367
|
+
1. WebGPU
|
|
368
|
+
2. WebGL2
|
|
369
|
+
3. Canvas2D
|
|
431
370
|
|
|
432
|
-
|
|
371
|
+
```ts
|
|
372
|
+
import { isWebGL2Supported, isWebGPUSupported } from 'libbitsub'
|
|
433
373
|
|
|
434
|
-
|
|
374
|
+
console.log({
|
|
375
|
+
webgpu: isWebGPUSupported(),
|
|
376
|
+
webgl2: isWebGL2Supported()
|
|
377
|
+
})
|
|
378
|
+
```
|
|
435
379
|
|
|
436
|
-
|
|
437
|
-
parser.loadPgs(pgsData)
|
|
380
|
+
## Notes
|
|
438
381
|
|
|
439
|
-
|
|
440
|
-
|
|
382
|
+
- Worker mode is shared, but subtitle parser state is isolated per renderer session.
|
|
383
|
+
- Multiple subtitle renderers can coexist without reusing the same parser instance.
|
|
384
|
+
- If worker startup fails, the high-level API falls back to main-thread parsing.
|
|
385
|
+
- The library only handles bitmap subtitle formats. It does not parse text subtitle formats such as SRT or ASS.
|
|
441
386
|
|
|
442
|
-
|
|
387
|
+
## API Reference
|
|
443
388
|
|
|
444
|
-
|
|
445
|
-
// ... render to canvas
|
|
389
|
+
### Top-level exports
|
|
446
390
|
|
|
447
|
-
|
|
448
|
-
|
|
391
|
+
- `initWasm(): Promise<void>` initializes the WASM module.
|
|
392
|
+
- `isWasmInitialized(): boolean` reports whether initialization has completed.
|
|
393
|
+
- `isWebGPUSupported(): boolean` checks WebGPU support.
|
|
394
|
+
- `detectSubtitleFormat(source: AutoSubtitleSource): 'pgs' | 'vobsub' | null` detects the bitmap subtitle format from file hints or binary data.
|
|
395
|
+
- `createAutoSubtitleRenderer(options: AutoVideoSubtitleOptions): PgsRenderer | VobSubRenderer` creates a high-level renderer after format detection.
|
|
396
|
+
- Legacy aliases remain exported: `PGSRenderer`, `VobsubRenderer`, `UnifiedSubtitleRenderer`.
|
|
449
397
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
### High-Level (Video-Integrated)
|
|
398
|
+
### High-level renderers
|
|
453
399
|
|
|
454
400
|
#### `PgsRenderer`
|
|
455
401
|
|
|
456
|
-
- `constructor(options: VideoSubtitleOptions)`
|
|
457
|
-
- `getDisplaySettings(): SubtitleDisplaySettings`
|
|
458
|
-
- `setDisplaySettings(settings: Partial<SubtitleDisplaySettings>): void`
|
|
459
|
-
- `resetDisplaySettings(): void`
|
|
460
|
-
- `getStats(): SubtitleRendererStats`
|
|
461
|
-
- `
|
|
402
|
+
- `constructor(options: VideoSubtitleOptions)` creates a video-synced PGS renderer.
|
|
403
|
+
- `getDisplaySettings(): SubtitleDisplaySettings` returns the current layout settings.
|
|
404
|
+
- `setDisplaySettings(settings: Partial<SubtitleDisplaySettings>): void` updates layout settings.
|
|
405
|
+
- `resetDisplaySettings(): void` resets layout settings to defaults.
|
|
406
|
+
- `getStats(): SubtitleRendererStats` returns render statistics.
|
|
407
|
+
- `getMetadata(): SubtitleParserMetadata | null` returns track-level metadata.
|
|
408
|
+
- `getCurrentCueMetadata(): SubtitleCueMetadata | null` returns the currently displayed cue metadata.
|
|
409
|
+
- `getCueMetadata(index: number): SubtitleCueMetadata | null` returns metadata for a specific cue.
|
|
410
|
+
- `getCacheLimit(): number` returns the active frame-cache limit.
|
|
411
|
+
- `setCacheLimit(limit: number): void` updates the frame-cache limit.
|
|
412
|
+
- `clearFrameCache(): void` clears the renderer-side and parser-side frame cache.
|
|
413
|
+
- `prefetchRange(startIndex: number, endIndex: number): Promise<void>` prefetches decoded frames for a cue range.
|
|
414
|
+
- `prefetchAroundTime(time: number, before?: number, after?: number): Promise<void>` prefetches around a playback time in seconds.
|
|
415
|
+
- `dispose(): void` releases DOM, parser, and worker resources.
|
|
462
416
|
|
|
463
417
|
#### `VobSubRenderer`
|
|
464
418
|
|
|
465
|
-
-
|
|
466
|
-
- `
|
|
467
|
-
- `
|
|
468
|
-
- `
|
|
469
|
-
- `
|
|
470
|
-
- `setDebandEnabled(enabled: boolean): void` - Enable/disable debanding filter
|
|
471
|
-
- `setDebandThreshold(threshold: number): void` - Set debanding threshold (0.0-255.0)
|
|
472
|
-
- `setDebandRange(range: number): void` - Set debanding sample range (1-64)
|
|
473
|
-
- `debandEnabled: boolean` - Check if debanding is enabled
|
|
474
|
-
- `dispose(): void` - Clean up all resources
|
|
419
|
+
- Supports all `PgsRenderer` methods above.
|
|
420
|
+
- `setDebandEnabled(enabled: boolean): void` enables or disables debanding.
|
|
421
|
+
- `setDebandThreshold(threshold: number): void` updates the deband threshold.
|
|
422
|
+
- `setDebandRange(range: number): void` updates the deband sample range.
|
|
423
|
+
- `debandEnabled: boolean` reports whether debanding is enabled.
|
|
475
424
|
|
|
476
|
-
### Low-
|
|
425
|
+
### Low-level parsers
|
|
477
426
|
|
|
478
427
|
#### `PgsParser`
|
|
479
428
|
|
|
480
|
-
- `load(data: Uint8Array): number`
|
|
481
|
-
- `getTimestamps(): Float64Array`
|
|
482
|
-
- `count: number`
|
|
483
|
-
- `findIndexAtTimestamp(timeSeconds: number): number`
|
|
484
|
-
- `renderAtIndex(index: number): SubtitleData | undefined`
|
|
485
|
-
- `renderAtTimestamp(timeSeconds: number): SubtitleData | undefined`
|
|
486
|
-
- `
|
|
487
|
-
- `
|
|
429
|
+
- `load(data: Uint8Array): number` loads PGS data and returns the cue count.
|
|
430
|
+
- `getTimestamps(): Float64Array` returns cue timestamps in milliseconds.
|
|
431
|
+
- `count: number` returns the number of cues.
|
|
432
|
+
- `findIndexAtTimestamp(timeSeconds: number): number` finds the cue index for a playback time in seconds.
|
|
433
|
+
- `renderAtIndex(index: number): SubtitleData | undefined` renders a cue by index.
|
|
434
|
+
- `renderAtTimestamp(timeSeconds: number): SubtitleData | undefined` renders a cue at a playback time.
|
|
435
|
+
- `getMetadata(): SubtitleParserMetadata` returns parser metadata.
|
|
436
|
+
- `getCueMetadata(index: number): SubtitleCueMetadata | null` returns cue metadata.
|
|
437
|
+
- `clearCache(): void` clears parser-side caches.
|
|
438
|
+
- `dispose(): void` frees parser resources.
|
|
488
439
|
|
|
489
440
|
#### `VobSubParserLowLevel`
|
|
490
441
|
|
|
491
|
-
- `loadFromData(idxContent: string, subData: Uint8Array): void`
|
|
492
|
-
- `loadFromSubOnly(subData: Uint8Array): void` -
|
|
493
|
-
- `
|
|
494
|
-
- `setDebandThreshold(threshold: number): void`
|
|
495
|
-
- `setDebandRange(range: number): void` - Set debanding sample range (1-64)
|
|
496
|
-
- `debandEnabled: boolean` - Check if debanding is enabled
|
|
497
|
-
- Same rendering methods as PgsParser
|
|
442
|
+
- `loadFromData(idxContent: string, subData: Uint8Array): void` loads IDX and SUB data.
|
|
443
|
+
- `loadFromSubOnly(subData: Uint8Array): void` loads SUB-only VobSub data.
|
|
444
|
+
- `getTimestamps(): Float64Array`, `count`, `findIndexAtTimestamp()`, `renderAtIndex()`, `renderAtTimestamp()`, `getMetadata()`, `getCueMetadata()`, `clearCache()`, and `dispose()` behave like `PgsParser`.
|
|
445
|
+
- `setDebandEnabled(enabled: boolean): void`, `setDebandThreshold(threshold: number): void`, `setDebandRange(range: number): void`, and `debandEnabled` control debanding.
|
|
498
446
|
|
|
499
447
|
#### `UnifiedSubtitleParser`
|
|
500
448
|
|
|
501
|
-
- `loadPgs(data: Uint8Array): number`
|
|
502
|
-
- `loadVobSub(idxContent: string, subData: Uint8Array): void`
|
|
503
|
-
- `loadVobSubOnly(subData: Uint8Array): void` -
|
|
504
|
-
- `
|
|
505
|
-
-
|
|
449
|
+
- `loadPgs(data: Uint8Array): number` loads PGS data.
|
|
450
|
+
- `loadVobSub(idxContent: string, subData: Uint8Array): void` loads VobSub from IDX and SUB.
|
|
451
|
+
- `loadVobSubOnly(subData: Uint8Array): void` loads SUB-only VobSub data.
|
|
452
|
+
- `loadAuto(source: AutoSubtitleSource): SubtitleFormatName` detects and loads a supported bitmap subtitle format.
|
|
453
|
+
- `format: 'pgs' | 'vobsub' | null` returns the active format.
|
|
454
|
+
- `getTimestamps()`, `count`, `findIndexAtTimestamp()`, `renderAtIndex()`, `renderAtTimestamp()`, `getMetadata()`, `getCueMetadata()`, `clearCache()`, and `dispose()` are available as on the format-specific parsers.
|
|
506
455
|
|
|
507
|
-
###
|
|
456
|
+
### Core option and data types
|
|
508
457
|
|
|
509
458
|
#### `VideoSubtitleOptions`
|
|
510
459
|
|
|
511
|
-
```
|
|
460
|
+
```ts
|
|
512
461
|
interface VideoSubtitleOptions {
|
|
513
|
-
video: HTMLVideoElement
|
|
514
|
-
subUrl?: string
|
|
515
|
-
subContent?: ArrayBuffer
|
|
516
|
-
workerUrl?: string
|
|
517
|
-
onLoading?: () => void
|
|
518
|
-
onLoaded?: () => void
|
|
519
|
-
onError?: (error: Error) => void
|
|
520
|
-
onWebGPUFallback?: () => void
|
|
521
|
-
onWebGL2Fallback?: () => void
|
|
462
|
+
video: HTMLVideoElement
|
|
463
|
+
subUrl?: string
|
|
464
|
+
subContent?: ArrayBuffer
|
|
465
|
+
workerUrl?: string
|
|
466
|
+
onLoading?: () => void
|
|
467
|
+
onLoaded?: () => void
|
|
468
|
+
onError?: (error: Error) => void
|
|
469
|
+
onWebGPUFallback?: () => void
|
|
470
|
+
onWebGL2Fallback?: () => void
|
|
471
|
+
displaySettings?: Partial<SubtitleDisplaySettings>
|
|
472
|
+
cacheLimit?: number
|
|
473
|
+
prefetchWindow?: {
|
|
474
|
+
before?: number
|
|
475
|
+
after?: number
|
|
476
|
+
}
|
|
477
|
+
onEvent?: (event: SubtitleRendererEvent) => void
|
|
522
478
|
}
|
|
523
479
|
```
|
|
524
480
|
|
|
525
481
|
#### `VideoVobSubOptions`
|
|
526
482
|
|
|
527
|
-
```
|
|
483
|
+
```ts
|
|
528
484
|
interface VideoVobSubOptions extends VideoSubtitleOptions {
|
|
529
|
-
idxUrl?: string
|
|
530
|
-
idxContent?: string
|
|
485
|
+
idxUrl?: string
|
|
486
|
+
idxContent?: string
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### `AutoVideoSubtitleOptions`
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
interface AutoVideoSubtitleOptions extends Omit<VideoVobSubOptions, 'subUrl' | 'idxUrl'> {
|
|
494
|
+
subUrl?: string
|
|
495
|
+
idxUrl?: string
|
|
496
|
+
fileName?: string
|
|
531
497
|
}
|
|
532
498
|
```
|
|
533
499
|
|
|
534
500
|
#### `SubtitleDisplaySettings`
|
|
535
501
|
|
|
536
|
-
```
|
|
502
|
+
```ts
|
|
537
503
|
interface SubtitleDisplaySettings {
|
|
538
|
-
// Scale factor (1.0 = 100%, 0.5 = 50%, 2.0 = 200%)
|
|
539
504
|
scale: number
|
|
540
|
-
// Vertical offset as % of video height (-50 to 50)
|
|
541
505
|
verticalOffset: number
|
|
506
|
+
horizontalOffset: number
|
|
507
|
+
horizontalAlign: 'left' | 'center' | 'right'
|
|
508
|
+
bottomPadding: number
|
|
509
|
+
safeArea: number
|
|
510
|
+
opacity: number
|
|
542
511
|
}
|
|
543
512
|
```
|
|
544
513
|
|
|
545
|
-
#### `
|
|
546
|
-
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
514
|
+
#### `SubtitleRendererEvent`
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
type SubtitleRendererEvent =
|
|
518
|
+
| { type: 'loading'; format: SubtitleFormatName }
|
|
519
|
+
| { type: 'loaded'; format: SubtitleFormatName; metadata: SubtitleParserMetadata }
|
|
520
|
+
| { type: 'error'; format: SubtitleFormatName; error: Error }
|
|
521
|
+
| { type: 'renderer-change'; renderer: 'webgpu' | 'webgl2' | 'canvas2d' }
|
|
522
|
+
| { type: 'worker-state'; enabled: boolean; ready: boolean; sessionId: string | null; fallback?: boolean }
|
|
523
|
+
| { type: 'cache-change'; cachedFrames: number; pendingRenders: number; cacheLimit: number }
|
|
524
|
+
| { type: 'cue-change'; cue: SubtitleCueMetadata | null }
|
|
525
|
+
| { type: 'stats'; stats: SubtitleRendererStatsSnapshot }
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### `SubtitleRendererStats` and `SubtitleRendererStatsSnapshot`
|
|
529
|
+
|
|
530
|
+
Both shapes expose:
|
|
531
|
+
|
|
532
|
+
- `framesRendered`
|
|
533
|
+
- `framesDropped`
|
|
534
|
+
- `avgRenderTime`
|
|
535
|
+
- `maxRenderTime`
|
|
536
|
+
- `minRenderTime`
|
|
537
|
+
- `lastRenderTime`
|
|
538
|
+
- `renderFps`
|
|
539
|
+
- `usingWorker`
|
|
540
|
+
- `cachedFrames`
|
|
541
|
+
- `pendingRenders`
|
|
542
|
+
- `totalEntries`
|
|
543
|
+
- `currentIndex`
|
|
544
|
+
|
|
545
|
+
#### `SubtitleParserMetadata`
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
interface SubtitleParserMetadata {
|
|
549
|
+
format: 'pgs' | 'vobsub'
|
|
550
|
+
cueCount: number
|
|
551
|
+
screenWidth: number
|
|
552
|
+
screenHeight: number
|
|
553
|
+
language?: string | null
|
|
554
|
+
trackId?: string | null
|
|
555
|
+
hasIdxMetadata?: boolean
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### `SubtitleCueMetadata`
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
interface SubtitleCueMetadata {
|
|
563
|
+
index: number
|
|
564
|
+
format: 'pgs' | 'vobsub'
|
|
565
|
+
startTime: number
|
|
566
|
+
endTime: number
|
|
567
|
+
duration: number
|
|
568
|
+
screenWidth: number
|
|
569
|
+
screenHeight: number
|
|
570
|
+
bounds: SubtitleCueBounds | null
|
|
571
|
+
compositionCount: number
|
|
572
|
+
paletteId?: number
|
|
573
|
+
compositionState?: number
|
|
574
|
+
language?: string | null
|
|
575
|
+
trackId?: string | null
|
|
576
|
+
filePosition?: number
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### `AutoSubtitleSource`
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
interface AutoSubtitleSource {
|
|
584
|
+
data?: ArrayBuffer | Uint8Array
|
|
585
|
+
subData?: ArrayBuffer | Uint8Array
|
|
586
|
+
idxContent?: string
|
|
587
|
+
fileName?: string
|
|
588
|
+
subUrl?: string
|
|
589
|
+
idxUrl?: string
|
|
561
590
|
}
|
|
562
591
|
```
|
|
563
592
|
|
|
564
593
|
#### `SubtitleData`
|
|
565
594
|
|
|
566
|
-
```
|
|
595
|
+
```ts
|
|
567
596
|
interface SubtitleData {
|
|
568
|
-
width: number
|
|
569
|
-
height: number
|
|
597
|
+
width: number
|
|
598
|
+
height: number
|
|
570
599
|
compositionData: SubtitleCompositionData[]
|
|
571
600
|
}
|
|
572
601
|
|
|
573
602
|
interface SubtitleCompositionData {
|
|
574
|
-
pixelData: ImageData
|
|
575
|
-
x: number
|
|
576
|
-
y: number
|
|
603
|
+
pixelData: ImageData
|
|
604
|
+
x: number
|
|
605
|
+
y: number
|
|
577
606
|
}
|
|
578
607
|
```
|
|
579
608
|
|