libbitsub 1.5.1 → 1.7.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 +262 -223
- 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 +54 -4
- package/dist/ts/renderers.d.ts.map +1 -1
- package/dist/ts/renderers.js +457 -87
- package/dist/ts/renderers.js.map +1 -1
- package/dist/ts/types.d.ts +147 -3
- 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 +57 -0
- package/dist/ts/webgl2-renderer.d.ts.map +1 -0
- package/dist/ts/webgl2-renderer.js +293 -0
- package/dist/ts/webgl2-renderer.js.map +1 -0
- package/dist/ts/webgpu-renderer.d.ts +5 -1
- package/dist/ts/webgpu-renderer.d.ts.map +1 -1
- package/dist/ts/webgpu-renderer.js +64 -45
- 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 +262 -223
- package/pkg/libbitsub.d.ts +120 -0
- package/pkg/libbitsub.js +251 -15
- package/pkg/libbitsub_bg.wasm +0 -0
- package/pkg/libbitsub_bg.wasm.d.ts +24 -0
- 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,314 +38,351 @@ 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
|
-
|
|
159
|
+
`SubtitleDisplaySettings`:
|
|
200
160
|
|
|
201
|
-
|
|
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 |
|
|
202
170
|
|
|
203
|
-
|
|
204
|
-
import { VobSubRenderer } from 'libbitsub'
|
|
171
|
+
## Metadata and introspection
|
|
205
172
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
173
|
+
High-level renderers expose parser and cue metadata:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const metadata = renderer.getMetadata()
|
|
177
|
+
const currentCue = renderer.getCurrentCueMetadata()
|
|
178
|
+
const cue42 = renderer.getCueMetadata(42)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Low-level parsers expose the same model:
|
|
210
182
|
|
|
211
|
-
|
|
212
|
-
|
|
183
|
+
```ts
|
|
184
|
+
import { PgsParser, UnifiedSubtitleParser, VobSubParserLowLevel } from 'libbitsub'
|
|
213
185
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
renderer.setDebandRange(15) // Pixel radius for sampling
|
|
186
|
+
const parser = new UnifiedSubtitleParser()
|
|
187
|
+
const detected = parser.loadAuto({ data: subtitleBytes, fileName: 'track.sup' })
|
|
217
188
|
|
|
218
|
-
|
|
219
|
-
console.log(
|
|
189
|
+
console.log(detected)
|
|
190
|
+
console.log(parser.getMetadata())
|
|
191
|
+
console.log(parser.getCueMetadata(0))
|
|
220
192
|
```
|
|
221
193
|
|
|
222
|
-
|
|
194
|
+
Metadata includes:
|
|
223
195
|
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
201
|
|
|
227
|
-
|
|
228
|
-
parser.loadFromData(idxContent, subData)
|
|
202
|
+
## Cache control and prefetching
|
|
229
203
|
|
|
230
|
-
|
|
231
|
-
parser.setDebandEnabled(true)
|
|
232
|
-
parser.setDebandThreshold(48.0)
|
|
233
|
-
parser.setDebandRange(12)
|
|
204
|
+
High-level renderers expose cache helpers:
|
|
234
205
|
|
|
235
|
-
|
|
236
|
-
|
|
206
|
+
```ts
|
|
207
|
+
renderer.setCacheLimit(48)
|
|
208
|
+
await renderer.prefetchRange(10, 20)
|
|
209
|
+
await renderer.prefetchAroundTime(videoElement.currentTime)
|
|
210
|
+
renderer.clearFrameCache()
|
|
237
211
|
```
|
|
238
212
|
|
|
239
|
-
|
|
213
|
+
`clearFrameCache()` clears both the renderer-side frame map and the underlying parser cache for the active session.
|
|
240
214
|
|
|
241
|
-
|
|
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 |
|
|
215
|
+
## Observability events
|
|
246
216
|
|
|
247
|
-
|
|
217
|
+
Use `onEvent` to observe renderer lifecycle and runtime behavior:
|
|
248
218
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Example: event-driven prefetch and cue inspection
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { PgsRenderer } from 'libbitsub'
|
|
244
|
+
|
|
245
|
+
const renderer = new PgsRenderer({
|
|
246
|
+
video: videoElement,
|
|
247
|
+
subUrl: '/subtitles/movie.sup',
|
|
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
|
+
}
|
|
280
|
+
})
|
|
253
281
|
|
|
254
|
-
|
|
282
|
+
videoElement.addEventListener('seeked', () => {
|
|
283
|
+
renderer.prefetchAroundTime(videoElement.currentTime).catch(console.error)
|
|
284
|
+
})
|
|
255
285
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
- `2.0` = 200%
|
|
260
|
-
- Range: `0.1` to `3.0`
|
|
286
|
+
// later
|
|
287
|
+
renderer.dispose()
|
|
288
|
+
```
|
|
261
289
|
|
|
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`
|
|
290
|
+
Emitted events:
|
|
267
291
|
|
|
268
|
-
|
|
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 |
|
|
269
302
|
|
|
270
|
-
|
|
303
|
+
## Performance stats
|
|
271
304
|
|
|
272
|
-
```
|
|
273
|
-
// Get performance statistics
|
|
305
|
+
```ts
|
|
274
306
|
const stats = renderer.getStats()
|
|
275
|
-
console.log(stats)
|
|
276
|
-
// Output:
|
|
277
|
-
// {
|
|
278
|
-
// framesRendered: 120,
|
|
279
|
-
// framesDropped: 2,
|
|
280
|
-
// avgRenderTime: 1.45,
|
|
281
|
-
// maxRenderTime: 8.32,
|
|
282
|
-
// minRenderTime: 0.12,
|
|
283
|
-
// lastRenderTime: 1.23,
|
|
284
|
-
// renderFps: 60,
|
|
285
|
-
// usingWorker: true,
|
|
286
|
-
// cachedFrames: 5,
|
|
287
|
-
// pendingRenders: 0,
|
|
288
|
-
// totalEntries: 847,
|
|
289
|
-
// currentIndex: 42
|
|
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)
|
|
303
307
|
```
|
|
304
308
|
|
|
305
|
-
|
|
309
|
+
`SubtitleRendererStats` includes:
|
|
306
310
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
| `totalEntries` | number | Total subtitle entries/display sets in the loaded file |
|
|
320
|
-
| `currentIndex` | number | Index of the currently displayed subtitle |
|
|
311
|
+
- `framesRendered`
|
|
312
|
+
- `framesDropped`
|
|
313
|
+
- `avgRenderTime`
|
|
314
|
+
- `maxRenderTime`
|
|
315
|
+
- `minRenderTime`
|
|
316
|
+
- `lastRenderTime`
|
|
317
|
+
- `renderFps`
|
|
318
|
+
- `usingWorker`
|
|
319
|
+
- `cachedFrames`
|
|
320
|
+
- `pendingRenders`
|
|
321
|
+
- `totalEntries`
|
|
322
|
+
- `currentIndex`
|
|
321
323
|
|
|
322
|
-
|
|
324
|
+
## Low-level APIs
|
|
323
325
|
|
|
324
|
-
|
|
326
|
+
### PGS parser
|
|
325
327
|
|
|
326
|
-
```
|
|
327
|
-
import {
|
|
328
|
+
```ts
|
|
329
|
+
import { PgsParser } from 'libbitsub'
|
|
328
330
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
console.log('WebGPU available - GPU-accelerated rendering enabled')
|
|
332
|
-
}
|
|
331
|
+
const parser = new PgsParser()
|
|
332
|
+
parser.load(new Uint8Array(arrayBuffer))
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
334
|
+
const timestamps = parser.getTimestamps()
|
|
335
|
+
const frame = parser.renderAtIndex(0)
|
|
336
|
+
const metadata = parser.getMetadata()
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### VobSub parser
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
import { VobSubParserLowLevel } from 'libbitsub'
|
|
343
|
+
|
|
344
|
+
const parser = new VobSubParserLowLevel()
|
|
345
|
+
parser.loadFromData(idxContent, new Uint8Array(subArrayBuffer))
|
|
346
|
+
parser.setDebandEnabled(true)
|
|
347
|
+
|
|
348
|
+
const frame = parser.renderAtTimestamp(120.5)
|
|
349
|
+
const cue = parser.getCueMetadata(0)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Unified parser
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { UnifiedSubtitleParser, detectSubtitleFormat } from 'libbitsub'
|
|
356
|
+
|
|
357
|
+
const format = detectSubtitleFormat({ data: subtitleBytes, fileName: 'track.sup' })
|
|
358
|
+
|
|
359
|
+
const parser = new UnifiedSubtitleParser()
|
|
360
|
+
parser.loadAuto({ data: subtitleBytes, fileName: 'track.sup' })
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## GPU backends
|
|
364
|
+
|
|
365
|
+
libbitsub prefers:
|
|
366
|
+
|
|
367
|
+
1. WebGPU
|
|
368
|
+
2. WebGL2
|
|
369
|
+
3. Canvas2D
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import { isWebGL2Supported, isWebGPUSupported } from 'libbitsub'
|
|
373
|
+
|
|
374
|
+
console.log({
|
|
375
|
+
webgpu: isWebGPUSupported(),
|
|
376
|
+
webgl2: isWebGL2Supported()
|
|
340
377
|
})
|
|
341
378
|
```
|
|
342
379
|
|
|
343
|
-
|
|
380
|
+
## Notes
|
|
344
381
|
|
|
345
|
-
-
|
|
346
|
-
-
|
|
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.
|
|
347
386
|
|
|
348
387
|
## Low-Level API (Programmatic Use)
|
|
349
388
|
|
|
@@ -501,11 +540,11 @@ interface VideoSubtitleOptions {
|
|
|
501
540
|
subUrl?: string // URL to subtitle file (provide this OR subContent)
|
|
502
541
|
subContent?: ArrayBuffer // Direct subtitle content (provide this OR subUrl)
|
|
503
542
|
workerUrl?: string // Worker URL (for API compatibility)
|
|
504
|
-
preferWebGPU?: boolean // Prefer WebGPU renderer if available (default: true)
|
|
505
543
|
onLoading?: () => void // Called when subtitle loading starts
|
|
506
544
|
onLoaded?: () => void // Called when subtitle loading completes
|
|
507
545
|
onError?: (error: Error) => void // Called when subtitle loading fails
|
|
508
|
-
onWebGPUFallback?: () => void // Called when WebGPU
|
|
546
|
+
onWebGPUFallback?: () => void // Called when WebGPU init fails
|
|
547
|
+
onWebGL2Fallback?: () => void // Called when WebGL2 init fails
|
|
509
548
|
}
|
|
510
549
|
```
|
|
511
550
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
export { PgsRenderer, VobSubRenderer, type VideoSubtitleOptions, type VideoVobSubOptions, type SubtitleRendererStats } from './wrapper';
|
|
6
|
+
export { PgsRenderer, VobSubRenderer, createAutoSubtitleRenderer, type VideoSubtitleOptions, type VideoVobSubOptions, type SubtitleRendererStats } from './wrapper';
|
|
7
7
|
export { PgsParser, VobSubParserLowLevel, UnifiedSubtitleParser } from './wrapper';
|
|
8
|
-
export { initWasm, isWasmInitialized, isWebGPUSupported, type SubtitleData, type SubtitleCompositionData, type SubtitleDisplaySettings } from './wrapper';
|
|
8
|
+
export { initWasm, isWasmInitialized, isWebGPUSupported, detectSubtitleFormat, type AutoSubtitleSource, type AutoVideoSubtitleOptions, type SubtitleCueBounds, type SubtitleCueMetadata, type SubtitleData, type SubtitleCompositionData, type SubtitleDisplaySettings, type SubtitleFormatName, type SubtitleHorizontalAlign, type SubtitleParserMetadata, type SubtitleRendererBackend, type SubtitleRendererEvent, type SubtitleRendererStatsSnapshot } from './wrapper';
|
|
9
9
|
export type { RenderResult, SubtitleFrame, VobSubFrame } from './wrapper';
|
|
10
10
|
export { PGSRenderer, VobsubRenderer, UnifiedSubtitleRenderer } from './wrapper';
|
|
11
11
|
//# 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;;;;GAIG;AAGH,OAAO,EACL,WAAW,EACX,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAGlF,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,WAAW,EACX,cAAc,EACd,0BAA0B,EAC1B,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAA;AAGlB,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAGlF,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EACnC,MAAM,WAAW,CAAA;AAGlB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAGzE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
// High-level video-integrated renderers (compatible with old libpgs-js API)
|
|
7
|
-
export { PgsRenderer, VobSubRenderer } from './wrapper';
|
|
7
|
+
export { PgsRenderer, VobSubRenderer, createAutoSubtitleRenderer } from './wrapper';
|
|
8
8
|
// Low-level parsers for programmatic use
|
|
9
9
|
export { PgsParser, VobSubParserLowLevel, UnifiedSubtitleParser } from './wrapper';
|
|
10
10
|
// Utility exports
|
|
11
|
-
export { initWasm, isWasmInitialized, isWebGPUSupported } from './wrapper';
|
|
11
|
+
export { initWasm, isWasmInitialized, isWebGPUSupported, detectSubtitleFormat } from './wrapper';
|
|
12
12
|
// Legacy aliases
|
|
13
13
|
export { PGSRenderer, VobsubRenderer, UnifiedSubtitleRenderer } from './wrapper';
|
|
14
14
|
//# 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;;;;GAIG;AAEH,4EAA4E;AAC5E,OAAO,EACL,WAAW,EACX,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4EAA4E;AAC5E,OAAO,EACL,WAAW,EACX,cAAc,EACd,0BAA0B,EAI3B,MAAM,WAAW,CAAA;AAElB,yCAAyC;AACzC,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAElF,kBAAkB;AAClB,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EAcrB,MAAM,WAAW,CAAA;AAKlB,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA"}
|