jassub 2.5.0 → 2.5.2

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.
@@ -8,7 +8,7 @@ import WASM from '../wasm/jassub-worker.js'
8
8
  import { Canvas2DRenderer } from './renderers/2d-renderer.ts'
9
9
  import { WebGL1Renderer } from './renderers/webgl1-renderer.ts'
10
10
  import { WebGL2Renderer } from './renderers/webgl2-renderer.ts'
11
- import { _applyKeys, _fetch, fetchtext, LIBASS_YCBCR_MAP, THREAD_COUNT, WEIGHT_MAP, type ASSEvent, type ASSImage, type ASSStyle, type WeightValue } from './util.ts'
11
+ import { _fetch, fetchtext, LIBASS_YCBCR_MAP, THREAD_COUNT, WEIGHT_MAP, type ASSEvent, type ASSImage, type ASSStyle, type WeightValue } from './util.ts'
12
12
 
13
13
  import type { JASSUB, MainModule } from '../wasm/types.d.ts'
14
14
  // import { WebGPURenderer } from './webgpu-renderer'
@@ -34,19 +34,22 @@ interface opts {
34
34
  queryFonts: 'local' | 'localandremote' | false
35
35
  }
36
36
 
37
+ const constructor = Symbol.for('constructor')
38
+
37
39
  export class ASSRenderer {
38
- _offCanvas?: OffscreenCanvas
39
40
  _wasm!: JASSUB
40
41
  _subtitleColorSpace?: 'BT601' | 'BT709' | 'SMPTE240M' | 'FCC' | null
41
42
  _videoColorSpace?: 'BT709' | 'BT601'
42
43
  _malloc!: (size: number) => number
43
- _gpurender: WebGL2Renderer | WebGL1Renderer | Canvas2DRenderer
44
+ _gpurender!: WebGL2Renderer | WebGL1Renderer | Canvas2DRenderer
44
45
 
45
46
  debug = false
46
47
 
47
- _ready
48
+ constructor (...args: [data: opts, getFont: (font: string, weight: WeightValue) => Promise<Uint8Array<ArrayBuffer> | undefined>, ctrl: OffscreenCanvas]) {
49
+ return this[constructor](...args) as unknown as this
50
+ }
48
51
 
49
- constructor (data: opts, getFont: (font: string, weight: WeightValue) => Promise<Uint8Array<ArrayBuffer> | undefined>) {
52
+ async [constructor] (data: opts, getFont: (font: string, weight: WeightValue) => Promise<Uint8Array<ArrayBuffer> | undefined>, ctrl: OffscreenCanvas) {
50
53
  // remove case sensitivity
51
54
  this._availableFonts = Object.fromEntries(Object.entries(data.availableFonts).map(([k, v]) => [k.trim().toLowerCase(), v]))
52
55
  this.debug = data.debug
@@ -58,17 +61,6 @@ export class ASSRenderer {
58
61
  const _fetch = globalThis.fetch
59
62
  globalThis.fetch = _ => _fetch(data.wasmUrl)
60
63
 
61
- // TODO: abslink doesnt support transferables yet
62
- const handleMessage = ({ data }: MessageEvent) => {
63
- if (data.name === 'offscreenCanvas') {
64
- // await this._ready // needed for webGPU
65
- this._offCanvas = data.ctrl
66
- this._gpurender.setCanvas(this._offCanvas!)
67
- removeEventListener('message', handleMessage)
68
- }
69
- }
70
- addEventListener('message', handleMessage)
71
-
72
64
  // const devicePromise = navigator.gpu?.requestAdapter({
73
65
  // powerPreference: 'high-performance'
74
66
  // }).then(adapter => adapter?.requestDevice())
@@ -83,33 +75,30 @@ export class ASSRenderer {
83
75
  this._gpurender = new Canvas2DRenderer()
84
76
  }
85
77
 
78
+ this._gpurender.setCanvas(ctrl)
79
+
80
+ this._loadedInitialFonts = !data.fonts.length
86
81
  // eslint-disable-next-line @typescript-eslint/unbound-method
87
- this._ready = (WASM({ __url: data.wasmUrl, __out: (log: string) => this._log(log) }) as Promise<MainModule>).then(async ({ _malloc, JASSUB }) => {
88
- this._malloc = _malloc
82
+ const { _malloc, JASSUB } = await (WASM({ __url: data.wasmUrl, __out: (log: string) => this._log(log) }) as Promise<MainModule>)
83
+ this._malloc = _malloc
89
84
 
90
- this._wasm = new JASSUB(data.width, data.height, this._defaultFont)
91
- // Firefox seems to have issues with multithreading in workers
92
- // a worker inside a worker does not recieve messages properly
93
- this._wasm.setThreads(THREAD_COUNT)
85
+ this._wasm = new JASSUB(data.width, data.height, this._defaultFont)
86
+ // Firefox seems to have issues with multithreading in workers
87
+ // a worker inside a worker does not recieve messages properly
88
+ this._wasm.setThreads(THREAD_COUNT)
94
89
 
95
- this._loadInitialFonts(data.fonts)
90
+ if (!this._loadedInitialFonts) await this._loadInitialFonts(data.fonts)
96
91
 
97
- this._wasm.createTrackMem(data.subContent ?? await fetchtext(data.subUrl!))
92
+ this._wasm.createTrackMem(data.subContent ?? await fetchtext(data.subUrl!))
98
93
 
99
- this._subtitleColorSpace = LIBASS_YCBCR_MAP[this._wasm.trackColorSpace]
94
+ this._subtitleColorSpace = LIBASS_YCBCR_MAP[this._wasm.trackColorSpace]
100
95
 
101
- if (data.libassMemoryLimit > 0 || data.libassGlyphLimit > 0) {
102
- this._wasm.setMemoryLimits(data.libassGlyphLimit || 0, data.libassMemoryLimit || 0)
103
- }
104
- // const device = await devicePromise
105
- // this._gpurender = device ? new WebGPURenderer(device) : new WebGL2Renderer()
106
- // if (this._offCanvas) this._gpurender.setCanvas(this._offCanvas, this._offCanvas.width, this._offCanvas.height)
107
- this._checkColorSpace()
108
- })
109
- }
96
+ if (data.libassMemoryLimit > 0 || data.libassGlyphLimit > 0) {
97
+ this._wasm.setMemoryLimits(data.libassGlyphLimit || 0, data.libassMemoryLimit || 0)
98
+ }
99
+ this._checkColorSpace()
110
100
 
111
- ready () {
112
- return this._ready
101
+ return this
113
102
  }
114
103
 
115
104
  // this passes a string of track data to libass, be it styles, events etc, which it then processes and adds to the track
@@ -119,20 +108,15 @@ export class ASSRenderer {
119
108
  }
120
109
 
121
110
  createEvent (event: ASSEvent) {
122
- _applyKeys(event, this._wasm.getEvent(this._wasm.allocEvent())!)
111
+ this._wasm.createEvent(event)
123
112
  }
124
113
 
125
- getEvents () {
126
- const events: Array<Partial<ASSEvent>> = []
127
- for (let i = 0; i < this._wasm.getEventCount(); i++) {
128
- const { Start, Duration, ReadOrder, Layer, Style, MarginL, MarginR, MarginV, Name, Text, Effect } = this._wasm.getEvent(i)!
129
- events.push({ Start, Duration, ReadOrder, Layer, Style, MarginL, MarginR, MarginV, Name, Text, Effect })
130
- }
131
- return events
114
+ getEvents (): Array<Partial<ASSEvent>> {
115
+ return this._wasm.getEvents()
132
116
  }
133
117
 
134
118
  setEvent (event: ASSEvent, index: number) {
135
- _applyKeys(event, this._wasm.getEvent(index)!)
119
+ this._wasm.setEvent(index, event)
136
120
  }
137
121
 
138
122
  removeEvent (index: number) {
@@ -140,24 +124,15 @@ export class ASSRenderer {
140
124
  }
141
125
 
142
126
  createStyle (style: ASSStyle) {
143
- const alloc = this._wasm.getStyle(this._wasm.allocStyle())!
144
- _applyKeys(style, alloc)
145
- return alloc
127
+ this._wasm.createStyle(style)
146
128
  }
147
129
 
148
- getStyles () {
149
- const styles: ASSStyle[] = []
150
- for (let i = 0; i < this._wasm.getStyleCount(); i++) {
151
- // eslint-disable-next-line @typescript-eslint/naming-convention
152
- const { Name, FontName, FontSize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding, treat_fontname_as_pattern, Blur, Justify } = this._wasm.getStyle(i)!
153
-
154
- styles.push({ Name, FontName, FontSize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding, treat_fontname_as_pattern, Blur, Justify })
155
- }
156
- return styles
130
+ getStyles (): ASSStyle[] {
131
+ return this._wasm.getStyles()
157
132
  }
158
133
 
159
134
  setStyle (style: ASSStyle, index: number) {
160
- _applyKeys(style, this._wasm.getStyle(index)!)
135
+ this._wasm.setStyle(index, style)
161
136
  }
162
137
 
163
138
  removeStyle (index: number) {
@@ -165,7 +140,7 @@ export class ASSRenderer {
165
140
  }
166
141
 
167
142
  styleOverride (style: ASSStyle) {
168
- this._wasm.styleOverride(this.createStyle(style))
143
+ this._wasm.styleOverride(style)
169
144
  }
170
145
 
171
146
  disableStyleOverride () {
@@ -191,7 +166,7 @@ export class ASSRenderer {
191
166
  this._gpurender.setColorMatrix(this._subtitleColorSpace, this._videoColorSpace)
192
167
  }
193
168
 
194
- _defaultFont
169
+ _defaultFont!: string
195
170
  setDefaultFont (fontName: string) {
196
171
  this._defaultFont = fontName.trim().toLowerCase()
197
172
  this._wasm.setDefaultFont(this._defaultFont)
@@ -206,7 +181,7 @@ export class ASSRenderer {
206
181
  }
207
182
 
208
183
  async addFonts (fontOrURLs: Array<Uint8Array | string>) {
209
- if (!fontOrURLs.length) return
184
+ if (!fontOrURLs.length) return false
210
185
  const strings: string[] = []
211
186
  const uint8s: Uint8Array[] = []
212
187
 
@@ -221,7 +196,7 @@ export class ASSRenderer {
221
196
 
222
197
  // this isn't batched like uint8s because software like jellyfin exists, which loads 50+ fonts over the network which takes time...
223
198
  // is connection exhaustion a concern here?
224
- return await Promise.allSettled(strings.map(url => this._asyncWrite(url)))
199
+ return !!await Promise.allSettled(strings.map(url => this._asyncWrite(url)))
225
200
  }
226
201
 
227
202
  // we don't want to run _findAvailableFont before initial fonts are loaded
@@ -230,9 +205,10 @@ export class ASSRenderer {
230
205
  async _loadInitialFonts (fontOrURLs: Array<Uint8Array | string>) {
231
206
  await this.addFonts(fontOrURLs)
232
207
  this._loadedInitialFonts = true
208
+ this._wasm.reloadFonts()
233
209
  }
234
210
 
235
- _getFont
211
+ _getFont!: (font: string, weight: WeightValue) => Promise<Uint8Array<ArrayBuffer> | undefined>
236
212
  _availableFonts: Record<string, Uint8Array | string> = {}
237
213
  _checkedFonts = new Set<string>()
238
214
  async _findAvailableFont (fontName: string, weight?: WeightValue) {
@@ -265,7 +241,7 @@ export class ASSRenderer {
265
241
  }
266
242
  }
267
243
 
268
- queryFonts
244
+ queryFonts!: 'local' | 'localandremote' | false
269
245
  async _queryLocalFont (fontName: string, weight: WeightValue) {
270
246
  if (!this.queryFonts) return
271
247
  return await this._getFont(fontName, weight)
@@ -302,7 +278,6 @@ export class ASSRenderer {
302
278
  }
303
279
 
304
280
  async [finalizer] () {
305
- await this._ready
306
281
  this._wasm.quitLibrary()
307
282
  this._gpurender.destroy()
308
283
  // @ts-expect-error force GC
@@ -313,26 +288,10 @@ export class ASSRenderer {
313
288
  }
314
289
 
315
290
  _draw (time: number, repaint = false) {
316
- if (!this._offCanvas || !this._gpurender) return
317
-
318
- const result = this._wasm.rawRender(time, Number(repaint))!
319
- if (this._wasm.changed === 0 && !repaint) return
320
-
321
- const bitmaps: ASSImage[] = []
322
-
323
- for (let image = result, i = 0; i < this._wasm.count; image = image.next!, ++i) {
324
- // @ts-expect-error internal emsc types
325
- bitmaps.push({
326
- bitmap: image.bitmap,
327
- color: image.color,
328
- dst_x: image.dst_x,
329
- dst_y: image.dst_y,
330
- h: image.h,
331
- stride: image.stride,
332
- w: image.w
333
- })
334
- }
335
- this._gpurender.render(bitmaps, self.HEAPU8RAW)
291
+ const images = this._wasm.rawRender(time, Number(repaint)) as ASSImage[] | null
292
+ if (!images) return
293
+
294
+ this._gpurender.render(images, self.HEAPU8RAW)
336
295
  }
337
296
 
338
297
  _setColorSpace (videoColorSpace: 'RGB' | 'BT709' | 'BT601') {