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.
Files changed (43) hide show
  1. package/README.md +262 -223
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/ts/parsers.d.ts +18 -1
  7. package/dist/ts/parsers.d.ts.map +1 -1
  8. package/dist/ts/parsers.js +151 -0
  9. package/dist/ts/parsers.js.map +1 -1
  10. package/dist/ts/renderers.d.ts +54 -4
  11. package/dist/ts/renderers.d.ts.map +1 -1
  12. package/dist/ts/renderers.js +457 -87
  13. package/dist/ts/renderers.js.map +1 -1
  14. package/dist/ts/types.d.ts +147 -3
  15. package/dist/ts/types.d.ts.map +1 -1
  16. package/dist/ts/utils.d.ts +11 -1
  17. package/dist/ts/utils.d.ts.map +1 -1
  18. package/dist/ts/utils.js +100 -1
  19. package/dist/ts/utils.js.map +1 -1
  20. package/dist/ts/wasm.js.map +1 -1
  21. package/dist/ts/webgl2-renderer.d.ts +57 -0
  22. package/dist/ts/webgl2-renderer.d.ts.map +1 -0
  23. package/dist/ts/webgl2-renderer.js +293 -0
  24. package/dist/ts/webgl2-renderer.js.map +1 -0
  25. package/dist/ts/webgpu-renderer.d.ts +5 -1
  26. package/dist/ts/webgpu-renderer.d.ts.map +1 -1
  27. package/dist/ts/webgpu-renderer.js +64 -45
  28. package/dist/ts/webgpu-renderer.js.map +1 -1
  29. package/dist/ts/worker.d.ts.map +1 -1
  30. package/dist/ts/worker.js +145 -87
  31. package/dist/ts/worker.js.map +1 -1
  32. package/dist/wrapper.d.ts +3 -2
  33. package/dist/wrapper.d.ts.map +1 -1
  34. package/dist/wrapper.js +3 -1
  35. package/dist/wrapper.js.map +1 -1
  36. package/package.json +3 -2
  37. package/pkg/README.md +262 -223
  38. package/pkg/libbitsub.d.ts +120 -0
  39. package/pkg/libbitsub.js +251 -15
  40. package/pkg/libbitsub_bg.wasm +0 -0
  41. package/pkg/libbitsub_bg.wasm.d.ts +24 -0
  42. package/pkg/package.json +1 -1
  43. 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 is re-engineered to maximize performance and extend functionality to VobSub, which was not supported by the original library. It remains fully backward compatible (only for PGS - obliviously). Special thanks to the original project for the inspiration!
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
- - **PGS (Blu-ray)** subtitle parsing and rendering
10
- - **VobSub (DVD)** subtitle parsing and rendering
11
- - **WebGPU rendering** GPU-accelerated rendering with automatic Canvas2D fallback
12
- - **High-performance** Rust-based rendering engine compiled to WebAssembly
13
- - **Zero-copy** data transfer between JS and WASM where possible
14
- - **Caching** for decoded bitmaps to optimize repeated rendering
15
- - **TypeScript** support with full type definitions
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 (Created using Spp2Pgs)
21
+ ### PGS
20
22
 
21
23
  https://gist.github.com/user-attachments/assets/55ac8e11-1964-4fb9-923e-dcac82dc7703
22
24
 
23
- ### Vobsub
25
+ ### VobSub
24
26
 
25
27
  https://gist.github.com/user-attachments/assets/a89ae9fe-23e4-4bc3-8cad-16a3f0fea665
26
28
 
27
- ### [See in action](https://a.rafasu.com/v)
29
+ ### Live demo
28
30
 
29
- ### Installation
31
+ https://a.rafasu.com/v
30
32
 
31
- **npm / bun**
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
- **JSR (Deno)**
41
+ For JSR:
40
42
 
41
43
  ```bash
42
44
  deno add jsr:@altq/libbitsub
43
45
  ```
44
46
 
45
- ### Setup for Web Workers (Recommended)
47
+ ## Worker setup
46
48
 
47
- For best performance with large subtitle files, copy the WASM files to your public folder so Web Workers can access them:
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
- This enables off-main-thread parsing which prevents UI freezing when loading large PGS files.
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
- ## Prerequisites
59
+ ## Building from source
59
60
 
60
- To build from source, you need:
61
+ Prerequisites:
61
62
 
62
- - [Rust](https://rustup.rs/) (1.70+)
63
- - [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/)
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
- ## Usage
72
+ ## Quick start
84
73
 
85
- ### Initialize WASM
74
+ Initialize the WASM module once before using any parser or renderer:
86
75
 
87
- Before using any renderer, you must initialize the WASM module:
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-Level API (Video Integration)
82
+ ## High-level video renderers
97
83
 
98
- The high-level API automatically handles video synchronization, canvas overlay, and subtitle fetching.
84
+ The high-level API manages subtitle loading, canvas overlay creation, playback sync, resize handling, worker usage, and renderer fallback.
99
85
 
100
- ### PGS Subtitles (Video-Integrated)
86
+ ### PGS renderer
101
87
 
102
- ```typescript
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
- workerUrl: '/libbitsub.js', // Optional, kept for API compatibility
110
- // Lifecycle callbacks (optional)
111
- onLoading: () => console.log('Loading subtitles...'),
112
- onLoaded: () => console.log('Subtitles loaded!'),
113
- onError: (error) => console.error('Failed to load:', error)
114
- })
115
-
116
- // Or load directly from ArrayBuffer
117
- const response = await fetch('/subtitles/movie.sup')
118
- const subtitleData = await response.arrayBuffer()
119
-
120
- const renderer = new PgsRenderer({
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 Subtitles (Video-Integrated)
111
+ ### VobSub renderer
139
112
 
140
- ```typescript
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', // Optional, defaults to .sub path with .idx extension
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
- // Or load directly from ArrayBuffer
159
- const [subResponse, idxResponse] = await Promise.all([fetch('/subtitles/movie.sub'), fetch('/subtitles/movie.idx')])
160
- const subData = await subResponse.arrayBuffer()
161
- const idxData = await idxResponse.text()
122
+ renderer.setDebandThreshold(64)
123
+ renderer.setDebandRange(15)
124
+ ```
162
125
 
163
- const renderer = new VobSubRenderer({
126
+ ### Automatic format detection
127
+
128
+ ```ts
129
+ import { createAutoSubtitleRenderer } from 'libbitsub'
130
+
131
+ const renderer = createAutoSubtitleRenderer({
164
132
  video: videoElement,
165
- subContent: subData, // Load .sub directly from ArrayBuffer
166
- idxContent: idxData, // Load .idx directly from string
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
- ### Subtitle Display Settings
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
- Both `PgsRenderer` and `VobSubRenderer` support real-time customization of subtitle size and position:
140
+ ## Layout controls
182
141
 
183
- ```typescript
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
- // Update settings
144
+ ```ts
190
145
  renderer.setDisplaySettings({
191
- scale: 1.2, // 1.2 = 120% size
192
- verticalOffset: -10 // -10% (move up 10% of video height)
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
- // Reset to defaults
155
+ const settings = renderer.getDisplaySettings()
196
156
  renderer.resetDisplaySettings()
197
157
  ```
198
158
 
199
- ### Debanding (VobSub)
159
+ `SubtitleDisplaySettings`:
200
160
 
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:
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
- ```typescript
204
- import { VobSubRenderer } from 'libbitsub'
171
+ ## Metadata and introspection
205
172
 
206
- const renderer = new VobSubRenderer({
207
- video: videoElement,
208
- subUrl: '/subtitles/movie.sub'
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
- // Debanding is enabled by default; call to disable if needed
212
- // renderer.setDebandEnabled(false)
183
+ ```ts
184
+ import { PgsParser, UnifiedSubtitleParser, VobSubParserLowLevel } from 'libbitsub'
213
185
 
214
- // Fine-tune debanding parameters
215
- renderer.setDebandThreshold(64.0) // Higher = more aggressive smoothing
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
- // Check if debanding is active
219
- console.log(renderer.debandEnabled) // true
189
+ console.log(detected)
190
+ console.log(parser.getMetadata())
191
+ console.log(parser.getCueMetadata(0))
220
192
  ```
221
193
 
222
- **Low-Level API:**
194
+ Metadata includes:
223
195
 
224
- ```typescript
225
- import { VobSubParserLowLevel } from 'libbitsub'
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
- const parser = new VobSubParserLowLevel()
228
- parser.loadFromData(idxContent, subData)
202
+ ## Cache control and prefetching
229
203
 
230
- // Configure debanding before rendering
231
- parser.setDebandEnabled(true)
232
- parser.setDebandThreshold(48.0)
233
- parser.setDebandRange(12)
204
+ High-level renderers expose cache helpers:
234
205
 
235
- // Rendered frames will have debanding applied
236
- const frame = parser.renderAtIndex(0)
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
- **Debanding Settings:**
213
+ `clearFrameCache()` clears both the renderer-side frame map and the underlying parser cache for the active session.
240
214
 
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 |
215
+ ## Observability events
246
216
 
247
- **Notes:**
217
+ Use `onEvent` to observe renderer lifecycle and runtime behavior:
248
218
 
249
- - Debanding is applied post-decode on the RGBA output
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)
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
- **Settings Reference:**
282
+ videoElement.addEventListener('seeked', () => {
283
+ renderer.prefetchAroundTime(videoElement.currentTime).catch(console.error)
284
+ })
255
285
 
256
- - `scale` (number): Scale factor for subtitles.
257
- - `1.0` = 100% (Original size)
258
- - `0.5` = 50%
259
- - `2.0` = 200%
260
- - Range: `0.1` to `3.0`
286
+ // later
287
+ renderer.dispose()
288
+ ```
261
289
 
262
- - `verticalOffset` (number): Vertical position offset as a percentage of video height.
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
- ### Performance Statistics
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
- Both `PgsRenderer` and `VobSubRenderer` provide real-time performance metrics:
303
+ ## Performance stats
271
304
 
272
- ```typescript
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
- **Stats Reference:**
309
+ `SubtitleRendererStats` includes:
306
310
 
307
- | Property | Type | Description |
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 |
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
- ### WebGPU Rendering
324
+ ## Low-level APIs
323
325
 
324
- libbitsub automatically uses WebGPU for GPU-accelerated rendering when available, with automatic fallback to Canvas2D:
326
+ ### PGS parser
325
327
 
326
- ```typescript
327
- import { PgsRenderer, isWebGPUSupported } from 'libbitsub'
328
+ ```ts
329
+ import { PgsParser } from 'libbitsub'
328
330
 
329
- // Check WebGPU support
330
- if (isWebGPUSupported()) {
331
- console.log('WebGPU available - GPU-accelerated rendering enabled')
332
- }
331
+ const parser = new PgsParser()
332
+ parser.load(new Uint8Array(arrayBuffer))
333
333
 
334
- // Configure WebGPU preference
335
- const renderer = new PgsRenderer({
336
- video: videoElement,
337
- subUrl: '/subtitles/movie.sup',
338
- preferWebGPU: true, // default: true
339
- onWebGPUFallback: () => console.log('Fell back to Canvas2D')
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
- **Options:**
380
+ ## Notes
344
381
 
345
- - `preferWebGPU` (boolean): Enable WebGPU rendering if available. Default: `true`
346
- - `onWebGPUFallback` (function): Callback when WebGPU is unavailable and falls back to Canvas2D
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 is unavailable
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
@@ -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,EAC7B,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"}
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,EAIf,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,EAIlB,MAAM,WAAW,CAAA;AAKlB,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA"}
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"}