akarisub 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +388 -0
- package/dist/COPYRIGHT +951 -0
- package/dist/akarisub-worker.js +39 -0
- package/dist/akarisub-worker.wasm +0 -0
- package/dist/akarisub.umd.js +159 -0
- package/dist/default.woff2 +0 -0
- package/dist/index.js +147 -0
- package/package.json +63 -0
- package/src/ts/akarisub.ts +1159 -0
- package/src/ts/types.ts +391 -0
- package/src/ts/utils.ts +512 -0
- package/src/ts/webgl2-renderer.ts +415 -0
- package/src/ts/webgpu-renderer.ts +728 -0
- package/src/ts/worker.ts +1866 -0
package/src/ts/types.ts
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for AkariSub TypeScript implementation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// ASS/SSA Subtitle Types
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
/** ASS Event (dialogue/subtitle entry) */
|
|
10
|
+
export interface ASSEvent {
|
|
11
|
+
/** Start Time of the Event (in seconds) */
|
|
12
|
+
Start: number
|
|
13
|
+
/** Duration of the Event (in seconds) */
|
|
14
|
+
Duration: number
|
|
15
|
+
/** Style name */
|
|
16
|
+
Style: string
|
|
17
|
+
/** Character name (for information only) */
|
|
18
|
+
Name: string
|
|
19
|
+
/** Left Margin override in pixels */
|
|
20
|
+
MarginL: number
|
|
21
|
+
/** Right Margin override in pixels */
|
|
22
|
+
MarginR: number
|
|
23
|
+
/** Bottom Margin override in pixels */
|
|
24
|
+
MarginV: number
|
|
25
|
+
/** Transition Effect */
|
|
26
|
+
Effect: string
|
|
27
|
+
/** Subtitle Text */
|
|
28
|
+
Text: string
|
|
29
|
+
/** Read order number */
|
|
30
|
+
ReadOrder: number
|
|
31
|
+
/** Z-index layer */
|
|
32
|
+
Layer: number
|
|
33
|
+
/** Internal index */
|
|
34
|
+
_index?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** ASS Style definition */
|
|
38
|
+
export interface ASSStyle {
|
|
39
|
+
/** Style name (case sensitive) */
|
|
40
|
+
Name: string
|
|
41
|
+
/** Font family name */
|
|
42
|
+
FontName: string
|
|
43
|
+
/** Font size */
|
|
44
|
+
FontSize: number
|
|
45
|
+
/** Primary color (RGBA as uint32) */
|
|
46
|
+
PrimaryColour: number
|
|
47
|
+
/** Secondary color (RGBA as uint32) */
|
|
48
|
+
SecondaryColour: number
|
|
49
|
+
/** Outline color (RGBA as uint32) */
|
|
50
|
+
OutlineColour: number
|
|
51
|
+
/** Background/shadow color (RGBA as uint32) */
|
|
52
|
+
BackColour: number
|
|
53
|
+
/** Bold (-1 = true, 0 = false) */
|
|
54
|
+
Bold: number
|
|
55
|
+
/** Italic (-1 = true, 0 = false) */
|
|
56
|
+
Italic: number
|
|
57
|
+
/** Underline (-1 = true, 0 = false) */
|
|
58
|
+
Underline: number
|
|
59
|
+
/** StrikeOut (-1 = true, 0 = false) */
|
|
60
|
+
StrikeOut: number
|
|
61
|
+
/** Width scale (percent) */
|
|
62
|
+
ScaleX: number
|
|
63
|
+
/** Height scale (percent) */
|
|
64
|
+
ScaleY: number
|
|
65
|
+
/** Extra spacing between characters (pixels) */
|
|
66
|
+
Spacing: number
|
|
67
|
+
/** Rotation angle (degrees) */
|
|
68
|
+
Angle: number
|
|
69
|
+
/** Border style (1 = outline + shadow, 3 = opaque box) */
|
|
70
|
+
BorderStyle: number
|
|
71
|
+
/** Outline width (0-4 pixels) */
|
|
72
|
+
Outline: number
|
|
73
|
+
/** Shadow depth (0-4 pixels) */
|
|
74
|
+
Shadow: number
|
|
75
|
+
/** Alignment (1-9, numpad style) */
|
|
76
|
+
Alignment: number
|
|
77
|
+
/** Left margin (pixels) */
|
|
78
|
+
MarginL: number
|
|
79
|
+
/** Right margin (pixels) */
|
|
80
|
+
MarginR: number
|
|
81
|
+
/** Vertical margin (pixels) */
|
|
82
|
+
MarginV: number
|
|
83
|
+
/** Font encoding */
|
|
84
|
+
Encoding: number
|
|
85
|
+
/** Treat font name as pattern */
|
|
86
|
+
treat_fontname_as_pattern: number
|
|
87
|
+
/** Blur amount */
|
|
88
|
+
Blur: number
|
|
89
|
+
/** Text justification */
|
|
90
|
+
Justify: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// Performance Stats Types
|
|
95
|
+
// =============================================================================
|
|
96
|
+
|
|
97
|
+
/** Performance statistics for the renderer */
|
|
98
|
+
export interface PerformanceStats {
|
|
99
|
+
/** Total frames rendered since reset */
|
|
100
|
+
framesRendered: number
|
|
101
|
+
/** Number of frames dropped */
|
|
102
|
+
framesDropped: number
|
|
103
|
+
/** Average render time in milliseconds */
|
|
104
|
+
avgRenderTime: number
|
|
105
|
+
/** Maximum render time in milliseconds */
|
|
106
|
+
maxRenderTime: number
|
|
107
|
+
/** Minimum render time in milliseconds */
|
|
108
|
+
minRenderTime: number
|
|
109
|
+
/** Last render time in milliseconds */
|
|
110
|
+
lastRenderTime: number
|
|
111
|
+
/** Estimated render FPS based on timing */
|
|
112
|
+
renderFps: number
|
|
113
|
+
/** Whether using Web Worker */
|
|
114
|
+
usingWorker: boolean
|
|
115
|
+
/** Whether offscreen rendering is enabled */
|
|
116
|
+
offscreenRender: boolean
|
|
117
|
+
/** Whether on-demand rendering is enabled */
|
|
118
|
+
onDemandRender: boolean
|
|
119
|
+
/** Number of pending render operations */
|
|
120
|
+
pendingRenders: number
|
|
121
|
+
/** Total subtitle events in current track */
|
|
122
|
+
totalEvents: number
|
|
123
|
+
/** Number of cache hits (unchanged frames) */
|
|
124
|
+
cacheHits: number
|
|
125
|
+
/** Number of cache misses (rendered frames) */
|
|
126
|
+
cacheMisses: number
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// AkariSub Options Types
|
|
131
|
+
// =============================================================================
|
|
132
|
+
|
|
133
|
+
/** Configuration options for AkariSub */
|
|
134
|
+
export interface AkariSubOptions {
|
|
135
|
+
/** Video element to sync with and overlay */
|
|
136
|
+
video?: HTMLVideoElement
|
|
137
|
+
/** Custom canvas element (optional if video is provided) */
|
|
138
|
+
canvas?: HTMLCanvasElement
|
|
139
|
+
/** Image blending mode: 'js' for hardware acceleration, 'wasm' for software */
|
|
140
|
+
blendMode?: 'js' | 'wasm'
|
|
141
|
+
/** Use async rendering with ImageBitmap (default: true) */
|
|
142
|
+
asyncRender?: boolean
|
|
143
|
+
/** Use offscreen canvas rendering (default: true) */
|
|
144
|
+
offscreenRender?: boolean
|
|
145
|
+
/** Use requestVideoFrameCallback for precise sync (default: true) */
|
|
146
|
+
onDemandRender?: boolean
|
|
147
|
+
/** Target FPS when not using onDemandRender (default: 24) */
|
|
148
|
+
targetFps?: number
|
|
149
|
+
/** Time offset in seconds (default: 0) */
|
|
150
|
+
timeOffset?: number
|
|
151
|
+
/** Enable debug logging (default: false) */
|
|
152
|
+
debug?: boolean
|
|
153
|
+
/** Scale factor for subtitles (default: 1.0) */
|
|
154
|
+
prescaleFactor?: number
|
|
155
|
+
/** Height limit for prescaling (default: 1080) */
|
|
156
|
+
prescaleHeightLimit?: number
|
|
157
|
+
/** Maximum render height, 0 = no limit (default: 0) */
|
|
158
|
+
maxRenderHeight?: number
|
|
159
|
+
/** Attempt to drop all animations (default: false) */
|
|
160
|
+
dropAllAnimations?: boolean
|
|
161
|
+
/** Drop all blur effects for performance (default: false) */
|
|
162
|
+
dropAllBlur?: boolean
|
|
163
|
+
/** Clamp \\pos values to script resolution (default: false) */
|
|
164
|
+
clampPos?: boolean
|
|
165
|
+
/** URL to the worker script */
|
|
166
|
+
workerUrl?: string
|
|
167
|
+
/** URL to the WASM binary */
|
|
168
|
+
wasmUrl?: string
|
|
169
|
+
/** URL to subtitle file */
|
|
170
|
+
subUrl?: string
|
|
171
|
+
/** Subtitle content as string */
|
|
172
|
+
subContent?: string
|
|
173
|
+
/** Array of font URLs or Uint8Arrays */
|
|
174
|
+
fonts?: (string | Uint8Array)[]
|
|
175
|
+
/** Available fonts map (lowercase name -> URL/data) */
|
|
176
|
+
availableFonts?: Record<string, string | Uint8Array>
|
|
177
|
+
/** Fallback font families in order (default: ['liberation sans']). Fontconfig uses these for cascade. */
|
|
178
|
+
fallbackFonts?: string[]
|
|
179
|
+
/** Use Local Font Access API (default: true if available) */
|
|
180
|
+
useLocalFonts?: boolean
|
|
181
|
+
/** libass bitmap cache memory limit in MiB */
|
|
182
|
+
libassMemoryLimit?: number
|
|
183
|
+
/** libass glyph cache limit */
|
|
184
|
+
libassGlyphLimit?: number
|
|
185
|
+
/** Callback invoked when all GPU renderers (WebGPU, WebGL2) are unavailable and the renderer falls back to Canvas2D */
|
|
186
|
+
onCanvasFallback?: () => void
|
|
187
|
+
/** Additional time in seconds to render subtitles ahead for pipeline latency compensation (default: 0.008) */
|
|
188
|
+
renderAhead?: number
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Callback Types (deprecated - use Promise-based API instead)
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
/** @deprecated Use Promise-based getEvents() instead */
|
|
196
|
+
export type ASSEventCallback = (error: Error | null, events: ASSEvent[]) => void
|
|
197
|
+
/** @deprecated Use Promise-based getStyles() instead */
|
|
198
|
+
export type ASSStyleCallback = (error: Error | null, styles: ASSStyle[]) => void
|
|
199
|
+
/** @deprecated Use Promise-based getStats() instead */
|
|
200
|
+
export type PerformanceStatsCallback = (error: Error | null, stats: PerformanceStats | null) => void
|
|
201
|
+
/** @deprecated Use Promise-based resetStats() instead */
|
|
202
|
+
export type ResetStatsCallback = (error: Error | null) => void
|
|
203
|
+
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Worker Message Types
|
|
206
|
+
// =============================================================================
|
|
207
|
+
|
|
208
|
+
/** Image data for rendering */
|
|
209
|
+
export interface RenderImage {
|
|
210
|
+
x: number
|
|
211
|
+
y: number
|
|
212
|
+
w: number
|
|
213
|
+
h: number
|
|
214
|
+
image: ImageBitmap | ArrayBuffer | number
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Render timing debug info */
|
|
218
|
+
export interface RenderTimes {
|
|
219
|
+
WASMRenderTime?: number
|
|
220
|
+
WASMBitmapDecodeTime?: number
|
|
221
|
+
JSRenderTime?: number
|
|
222
|
+
JSBitmapGenerationTime?: number
|
|
223
|
+
IPCTime?: number
|
|
224
|
+
bitmaps?: number
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Worker -> Main thread message for rendering */
|
|
228
|
+
export interface RenderMessage {
|
|
229
|
+
target: 'render'
|
|
230
|
+
asyncRender: boolean
|
|
231
|
+
images: RenderImage[]
|
|
232
|
+
times: RenderTimes
|
|
233
|
+
width: number
|
|
234
|
+
height: number
|
|
235
|
+
colorSpace: string | null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Worker -> Main thread messages */
|
|
239
|
+
export type WorkerOutboundMessage =
|
|
240
|
+
| { target: 'ready' }
|
|
241
|
+
| { target: 'unbusy' }
|
|
242
|
+
| { target: 'console'; command: string; content: string }
|
|
243
|
+
| { target: 'getLocalFont'; font: string }
|
|
244
|
+
| { target: 'verifyColorSpace'; subtitleColorSpace: string | null }
|
|
245
|
+
| { target: 'getEvents'; events: ASSEvent[] }
|
|
246
|
+
| { target: 'getStyles'; styles: ASSStyle[]; time: number }
|
|
247
|
+
| { target: 'getStats'; stats: Partial<PerformanceStats> }
|
|
248
|
+
| { target: 'resetStats'; success: boolean }
|
|
249
|
+
| { target: 'getEventCount'; count: number }
|
|
250
|
+
| { target: 'getStyleCount'; count: number }
|
|
251
|
+
| RenderMessage
|
|
252
|
+
|
|
253
|
+
/** Main thread -> Worker init message */
|
|
254
|
+
export interface WorkerInitMessage {
|
|
255
|
+
target: 'init'
|
|
256
|
+
wasmUrl: string
|
|
257
|
+
asyncRender: boolean
|
|
258
|
+
onDemandRender: boolean
|
|
259
|
+
initialTime: number
|
|
260
|
+
width: number
|
|
261
|
+
height: number
|
|
262
|
+
blendMode: 'js' | 'wasm'
|
|
263
|
+
subUrl?: string
|
|
264
|
+
subContent?: string | null
|
|
265
|
+
fonts: (string | Uint8Array)[]
|
|
266
|
+
availableFonts: Record<string, string | Uint8Array>
|
|
267
|
+
fallbackFonts: string[]
|
|
268
|
+
debug: boolean
|
|
269
|
+
targetFps: number
|
|
270
|
+
dropAllAnimations?: boolean
|
|
271
|
+
dropAllBlur?: boolean
|
|
272
|
+
clampPos?: boolean
|
|
273
|
+
libassMemoryLimit?: number
|
|
274
|
+
libassGlyphLimit?: number
|
|
275
|
+
useLocalFonts: boolean
|
|
276
|
+
hasBitmapBug: boolean
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Main thread -> Worker messages */
|
|
280
|
+
export type WorkerInboundMessage =
|
|
281
|
+
| WorkerInitMessage
|
|
282
|
+
| { target: 'offscreenCanvas'; transferable: [OffscreenCanvas] }
|
|
283
|
+
| { target: 'detachOffscreen' }
|
|
284
|
+
| { target: 'canvas'; width: number; height: number; videoWidth: number; videoHeight: number; force?: boolean }
|
|
285
|
+
| { target: 'video'; currentTime?: number; isPaused?: boolean; rate?: number; colorSpace?: string | null }
|
|
286
|
+
| { target: 'setTrack'; content: string }
|
|
287
|
+
| { target: 'setTrackByUrl'; url: string }
|
|
288
|
+
| { target: 'freeTrack' }
|
|
289
|
+
| { target: 'demand'; time: number }
|
|
290
|
+
| { target: 'destroy' }
|
|
291
|
+
| { target: 'addFont'; font: string | Uint8Array }
|
|
292
|
+
| { target: 'defaultFont'; font: string }
|
|
293
|
+
| { target: 'createEvent'; event: Partial<ASSEvent> }
|
|
294
|
+
| { target: 'setEvent'; event: Partial<ASSEvent>; index: number }
|
|
295
|
+
| { target: 'removeEvent'; index: number }
|
|
296
|
+
| { target: 'getEvents' }
|
|
297
|
+
| { target: 'createStyle'; style: Partial<ASSStyle> }
|
|
298
|
+
| { target: 'setStyle'; style: Partial<ASSStyle>; index: number }
|
|
299
|
+
| { target: 'removeStyle'; index: number }
|
|
300
|
+
| { target: 'getStyles' }
|
|
301
|
+
| { target: 'styleOverride'; style: Partial<ASSStyle> }
|
|
302
|
+
| { target: 'disableStyleOverride' }
|
|
303
|
+
| { target: 'getStats' }
|
|
304
|
+
| { target: 'resetStats' }
|
|
305
|
+
| { target: 'getEventCount' }
|
|
306
|
+
| { target: 'getStyleCount' }
|
|
307
|
+
| { target: 'runBenchmark' }
|
|
308
|
+
| { target: 'getColorSpace' }
|
|
309
|
+
|
|
310
|
+
// =============================================================================
|
|
311
|
+
// RVFC Types
|
|
312
|
+
// =============================================================================
|
|
313
|
+
|
|
314
|
+
/** requestVideoFrameCallback metadata */
|
|
315
|
+
export interface VideoFrameCallbackMetadata {
|
|
316
|
+
/** The current media time of the frame being displayed (seconds) */
|
|
317
|
+
mediaTime: number
|
|
318
|
+
/** Video intrinsic width */
|
|
319
|
+
width: number
|
|
320
|
+
/** Video intrinsic height */
|
|
321
|
+
height: number
|
|
322
|
+
/** Number of frames presented so far */
|
|
323
|
+
presentedFrames?: number
|
|
324
|
+
/** Time spent processing the frame (milliseconds) */
|
|
325
|
+
processingDuration?: number
|
|
326
|
+
/** Expected time when this frame will be displayed (DOMHighResTimeStamp) */
|
|
327
|
+
expectedDisplayTime?: number
|
|
328
|
+
/** Time at which the frame was presented (DOMHighResTimeStamp) */
|
|
329
|
+
presentationTime?: number
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// Color Space Types
|
|
334
|
+
// =============================================================================
|
|
335
|
+
|
|
336
|
+
export type WebYCbCrColorSpace = 'BT709' | 'BT601'
|
|
337
|
+
export type SubtitleColorSpace = 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC' | null
|
|
338
|
+
|
|
339
|
+
// =============================================================================
|
|
340
|
+
// AkariSub WASM Module Types
|
|
341
|
+
// =============================================================================
|
|
342
|
+
|
|
343
|
+
/** Emscripten AkariSub Module (C ABI exports) */
|
|
344
|
+
export interface AkariSubModule extends EmscriptenModule {
|
|
345
|
+
_malloc: (size: number) => number
|
|
346
|
+
_free: (ptr: number) => void
|
|
347
|
+
_akarisub_create: (width: number, height: number, fallbackFontPtr: number, debug: number) => number
|
|
348
|
+
_akarisub_destroy: (handle: number) => void
|
|
349
|
+
_akarisub_set_drop_animations: (handle: number, value: number) => void
|
|
350
|
+
_akarisub_create_track_mem: (handle: number, contentPtr: number) => void
|
|
351
|
+
_akarisub_remove_track: (handle: number) => void
|
|
352
|
+
_akarisub_resize_canvas: (handle: number, width: number, height: number, videoWidth: number, videoHeight: number) => void
|
|
353
|
+
_akarisub_add_font: (handle: number, namePtr: number, dataPtr: number, size: number) => void
|
|
354
|
+
_akarisub_reload_fonts: (handle: number) => void
|
|
355
|
+
_akarisub_set_default_font: (handle: number, fontPtr: number) => void
|
|
356
|
+
_akarisub_set_fallback_fonts: (handle: number, fontsPtr: number) => void
|
|
357
|
+
_akarisub_set_memory_limits: (handle: number, glyphLimit: number, memoryLimit: number) => void
|
|
358
|
+
_akarisub_get_event_count: (handle: number) => number
|
|
359
|
+
_akarisub_alloc_event: (handle: number) => number
|
|
360
|
+
_akarisub_remove_event: (handle: number, index: number) => void
|
|
361
|
+
_akarisub_get_style_count: (handle: number) => number
|
|
362
|
+
_akarisub_alloc_style: (handle: number) => number
|
|
363
|
+
_akarisub_remove_style: (handle: number, index: number) => void
|
|
364
|
+
_akarisub_style_override_index: (handle: number, index: number) => void
|
|
365
|
+
_akarisub_disable_style_override: (handle: number) => void
|
|
366
|
+
_akarisub_render_blend: (handle: number, time: number, force: number) => number
|
|
367
|
+
_akarisub_render_image: (handle: number, time: number, force: number) => number
|
|
368
|
+
_akarisub_get_changed: (handle: number) => number
|
|
369
|
+
_akarisub_get_count: (handle: number) => number
|
|
370
|
+
_akarisub_get_time: (handle: number) => number
|
|
371
|
+
_akarisub_get_track_color_space: (handle: number) => number
|
|
372
|
+
_akarisub_event_get_int: (handle: number, index: number, field: number) => number
|
|
373
|
+
_akarisub_event_set_int: (handle: number, index: number, field: number, value: number) => void
|
|
374
|
+
_akarisub_event_get_str: (handle: number, index: number, field: number) => number
|
|
375
|
+
_akarisub_event_set_str: (handle: number, index: number, field: number, valuePtr: number) => void
|
|
376
|
+
_akarisub_style_get_num: (handle: number, index: number, field: number) => number
|
|
377
|
+
_akarisub_style_set_num: (handle: number, index: number, field: number, value: number) => void
|
|
378
|
+
_akarisub_style_get_str: (handle: number, index: number, field: number) => number
|
|
379
|
+
_akarisub_style_set_str: (handle: number, index: number, field: number, valuePtr: number) => void
|
|
380
|
+
_akarisub_render_result_x: (resultPtr: number) => number
|
|
381
|
+
_akarisub_render_result_y: (resultPtr: number) => number
|
|
382
|
+
_akarisub_render_result_w: (resultPtr: number) => number
|
|
383
|
+
_akarisub_render_result_h: (resultPtr: number) => number
|
|
384
|
+
_akarisub_render_result_image: (resultPtr: number) => number
|
|
385
|
+
_akarisub_render_result_next: (resultPtr: number) => number
|
|
386
|
+
_akarisub_render_result_collect: (resultPtr: number, outPtr: number, maxItems: number) => number
|
|
387
|
+
_akarisub_render_blend_collect: (handle: number, time: number, force: number, outPtr: number, maxItems: number) => number
|
|
388
|
+
_akarisub_render_image_collect: (handle: number, time: number, force: number, outPtr: number, maxItems: number) => number
|
|
389
|
+
FS_createPath: (parent: string, path: string, canRead: boolean, canWrite: boolean) => void
|
|
390
|
+
FS_createDataFile: (parent: string, name: string | null, data: Uint8Array, canRead: boolean, canWrite: boolean, canOwn?: boolean) => void
|
|
391
|
+
}
|