jassub 2.2.7 → 2.3.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 +52 -0
- package/dist/default.woff2 +0 -0
- package/dist/jassub.d.ts +12 -12
- package/dist/jassub.js +27 -40
- package/dist/jassub.js.map +1 -1
- package/dist/wasm/jassub-worker.js +4 -0
- package/dist/wasm/jassub-worker.js.map +1 -1
- package/dist/worker/util.d.ts +10 -4
- package/dist/worker/util.js +20 -20
- package/dist/worker/util.js.map +1 -1
- package/dist/worker/webgl-renderer.d.ts +1 -1
- package/dist/worker/webgl-renderer.js +1 -1
- package/dist/worker/webgl-renderer.js.map +1 -1
- package/dist/worker/worker.d.ts +24 -19
- package/dist/worker/worker.js +107 -68
- package/dist/worker/worker.js.map +1 -1
- package/package.json +5 -2
- package/src/jassub.ts +42 -47
- package/src/worker/util.ts +30 -22
- package/src/worker/webgl-renderer.ts +1 -3
- package/src/worker/worker.ts +113 -76
package/src/worker/worker.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/* eslint-disable camelcase */
|
|
2
2
|
import { finalizer } from 'abslink'
|
|
3
3
|
import { expose } from 'abslink/w3c'
|
|
4
|
+
import { queryRemoteFonts } from 'lfa-ponyfill'
|
|
4
5
|
|
|
5
6
|
import WASM from '../wasm/jassub-worker.js'
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import { WebGL2Renderer } from './webgl-renderer'
|
|
9
|
-
// import { WebGPURenderer } from './webgpu-renderer'
|
|
10
|
-
|
|
11
|
-
import type { ASSEvent, ASSImage, ASSStyle } from '../jassub'
|
|
12
|
-
import type { JASSUB, MainModule } from '../wasm/types.js'
|
|
8
|
+
import { _applyKeys, _fetch, fetchtext, IS_FIREFOX, LIBASS_YCBCR_MAP, WEIGHT_MAP, type ASSEvent, type ASSImage, type ASSStyle, type WeightValue } from './util.ts'
|
|
9
|
+
import { WebGL2Renderer } from './webgl-renderer.ts'
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
import type { JASSUB, MainModule } from '../wasm/types.d.ts'
|
|
12
|
+
// import { WebGPURenderer } from './webgpu-renderer'
|
|
15
13
|
|
|
16
14
|
declare const self: DedicatedWorkerGlobalScope &
|
|
17
15
|
typeof globalThis & {
|
|
@@ -27,11 +25,11 @@ interface opts {
|
|
|
27
25
|
subContent: string | null
|
|
28
26
|
fonts: Array<string | Uint8Array>
|
|
29
27
|
availableFonts: Record<string, Uint8Array | string>
|
|
30
|
-
|
|
28
|
+
defaultFont: string
|
|
31
29
|
debug: boolean
|
|
32
30
|
libassMemoryLimit: number
|
|
33
31
|
libassGlyphLimit: number
|
|
34
|
-
|
|
32
|
+
queryFonts: 'local' | 'localandremote' | false
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
export class ASSRenderer {
|
|
@@ -43,19 +41,16 @@ export class ASSRenderer {
|
|
|
43
41
|
_gpurender = new WebGL2Renderer()
|
|
44
42
|
|
|
45
43
|
debug = false
|
|
46
|
-
useLocalFonts = false
|
|
47
|
-
_availableFonts: Record<string, Uint8Array | string> = {}
|
|
48
|
-
_fontMap: Record<string, boolean> = {}
|
|
49
|
-
_fontId = 0
|
|
50
44
|
|
|
51
45
|
_ready
|
|
52
|
-
_getFont
|
|
53
46
|
|
|
54
|
-
constructor (data: opts, getFont: (font: string) => Promise<
|
|
55
|
-
|
|
47
|
+
constructor (data: opts, getFont: (font: string, weight: WeightValue) => Promise<Uint8Array<ArrayBuffer> | undefined>) {
|
|
48
|
+
// remove case sensitivity
|
|
49
|
+
this._availableFonts = Object.fromEntries(Object.entries(data.availableFonts).map(([k, v]) => [k.trim().toLowerCase(), v]))
|
|
56
50
|
this.debug = data.debug
|
|
57
|
-
this.
|
|
51
|
+
this.queryFonts = data.queryFonts
|
|
58
52
|
this._getFont = getFont
|
|
53
|
+
this._defaultFont = data.defaultFont.trim().toLowerCase()
|
|
59
54
|
|
|
60
55
|
// hack, we want custom WASM URLs
|
|
61
56
|
const _fetch = globalThis.fetch
|
|
@@ -76,26 +71,20 @@ export class ASSRenderer {
|
|
|
76
71
|
// powerPreference: 'high-performance'
|
|
77
72
|
// }).then(adapter => adapter?.requestDevice())
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this._malloc =
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
75
|
+
this._ready = (WASM({ __url: data.wasmUrl, __out: (log: string) => this._log(log) }) as Promise<MainModule>).then(async ({ _malloc, JASSUB }) => {
|
|
76
|
+
this._malloc = _malloc
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
this._wasm = new Module.JASSUB(data.width, data.height, fallbackFont)
|
|
78
|
+
this._wasm = new JASSUB(data.width, data.height, this._defaultFont)
|
|
85
79
|
// Firefox seems to have issues with multithreading in workers
|
|
86
|
-
// a worker inside a worker does not
|
|
80
|
+
// a worker inside a worker does not recieve messages properly
|
|
87
81
|
this._wasm.setThreads(!IS_FIREFOX && self.crossOriginIsolated ? Math.min(Math.max(1, navigator.hardwareConcurrency - 2), 8) : 1)
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const subContent = data.subContent ?? read_(data.subUrl!)
|
|
83
|
+
this._loadInitialFonts(data.fonts)
|
|
92
84
|
|
|
93
|
-
|
|
85
|
+
this._wasm.createTrackMem(data.subContent ?? await fetchtext(data.subUrl!))
|
|
94
86
|
|
|
95
|
-
this._wasm.
|
|
96
|
-
this._processAvailableFonts(subContent)
|
|
97
|
-
|
|
98
|
-
this._subtitleColorSpace = libassYCbCrMap[this._wasm.trackColorSpace]
|
|
87
|
+
this._subtitleColorSpace = LIBASS_YCBCR_MAP[this._wasm.trackColorSpace]
|
|
99
88
|
|
|
100
89
|
if (data.libassMemoryLimit > 0 || data.libassGlyphLimit > 0) {
|
|
101
90
|
this._wasm.setMemoryLimits(data.libassGlyphLimit || 0, data.libassMemoryLimit || 0)
|
|
@@ -111,10 +100,6 @@ export class ASSRenderer {
|
|
|
111
100
|
return this._ready
|
|
112
101
|
}
|
|
113
102
|
|
|
114
|
-
addFont (fontOrURL: Uint8Array | string) {
|
|
115
|
-
this._asyncWrite(fontOrURL)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
103
|
createEvent (event: ASSEvent) {
|
|
119
104
|
_applyKeys(event, this._wasm.getEvent(this._wasm.allocEvent())!)
|
|
120
105
|
}
|
|
@@ -169,23 +154,18 @@ export class ASSRenderer {
|
|
|
169
154
|
this._wasm.disableStyleOverride()
|
|
170
155
|
}
|
|
171
156
|
|
|
172
|
-
setDefaultFont (fontName: string) {
|
|
173
|
-
this._wasm.setDefaultFont(fontName)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
157
|
setTrack (content: string) {
|
|
177
158
|
this._wasm.createTrackMem(content)
|
|
178
|
-
this._processAvailableFonts(content)
|
|
179
159
|
|
|
180
|
-
this._subtitleColorSpace =
|
|
160
|
+
this._subtitleColorSpace = LIBASS_YCBCR_MAP[this._wasm.trackColorSpace]!
|
|
181
161
|
}
|
|
182
162
|
|
|
183
163
|
freeTrack () {
|
|
184
164
|
this._wasm.removeTrack()
|
|
185
165
|
}
|
|
186
166
|
|
|
187
|
-
setTrackByUrl (url: string) {
|
|
188
|
-
this.setTrack(
|
|
167
|
+
async setTrackByUrl (url: string) {
|
|
168
|
+
this.setTrack(await fetchtext(url))
|
|
189
169
|
}
|
|
190
170
|
|
|
191
171
|
_checkColorSpace () {
|
|
@@ -193,52 +173,109 @@ export class ASSRenderer {
|
|
|
193
173
|
this._gpurender.setColorMatrix(this._subtitleColorSpace, this._videoColorSpace)
|
|
194
174
|
}
|
|
195
175
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
176
|
+
_defaultFont
|
|
177
|
+
setDefaultFont (fontName: string) {
|
|
178
|
+
this._defaultFont = fontName.trim().toLowerCase()
|
|
179
|
+
this._wasm.setDefaultFont(this._defaultFont)
|
|
180
|
+
}
|
|
200
181
|
|
|
201
|
-
|
|
182
|
+
async _log (log: string) {
|
|
183
|
+
console.debug(log)
|
|
184
|
+
const match = log.match(/JASSUB: fontselect:[^(]+: \(([^,]+), (\d{1,4}), \d\)/)
|
|
185
|
+
if (match && !await this._findAvailableFont(match[1]!.trim().toLowerCase(), WEIGHT_MAP[parseInt(match[2]!, 10) / 100 - 1])) {
|
|
186
|
+
await this._findAvailableFont(this._defaultFont)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
202
189
|
|
|
203
|
-
|
|
190
|
+
async addFonts (fontOrURLs: Array<Uint8Array | string>) {
|
|
191
|
+
if (!fontOrURLs.length) return
|
|
192
|
+
const strings: string[] = []
|
|
193
|
+
const uint8s: Uint8Array[] = []
|
|
204
194
|
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
195
|
+
for (const fontOrURL of fontOrURLs) {
|
|
196
|
+
if (typeof fontOrURL === 'string') {
|
|
197
|
+
strings.push(fontOrURL)
|
|
198
|
+
} else {
|
|
199
|
+
uint8s.push(fontOrURL)
|
|
200
|
+
}
|
|
209
201
|
}
|
|
202
|
+
if (uint8s.length) this._allocFonts(uint8s)
|
|
203
|
+
|
|
204
|
+
// this isn't batched like uint8s because software like jellyfin exists, which loads 50+ fonts over the network which takes time...
|
|
205
|
+
// is connection exhaustion a concern here?
|
|
206
|
+
return await Promise.allSettled(strings.map(url => this._asyncWrite(url)))
|
|
210
207
|
}
|
|
211
208
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
// we don't want to run _findAvailableFont before initial fonts are loaded
|
|
210
|
+
// because it could duplicate fonts
|
|
211
|
+
_loadedInitialFonts = false
|
|
212
|
+
async _loadInitialFonts (fontOrURLs: Array<Uint8Array | string>) {
|
|
213
|
+
await this.addFonts(fontOrURLs)
|
|
214
|
+
this._loadedInitialFonts = true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_getFont
|
|
218
|
+
_availableFonts: Record<string, Uint8Array | string> = {}
|
|
219
|
+
_checkedFonts = new Set<string>()
|
|
220
|
+
async _findAvailableFont (fontName: string, weight?: WeightValue) {
|
|
221
|
+
if (!this._loadedInitialFonts) return
|
|
222
|
+
|
|
223
|
+
// Roboto Medium, null -> Roboto, Medium
|
|
224
|
+
// Roboto Medium, Medium -> Roboto, Medium
|
|
225
|
+
// Roboto, null -> Roboto, Regular
|
|
226
|
+
// italic is not handled I guess
|
|
227
|
+
for (const _weight of WEIGHT_MAP) {
|
|
228
|
+
// check if fontname has this weight name in it, if yes remove it
|
|
229
|
+
if (fontName.includes(_weight)) {
|
|
230
|
+
fontName = fontName.replace(_weight, '').trim()
|
|
231
|
+
weight ??= _weight
|
|
232
|
+
break
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
weight ??= 'regular'
|
|
237
|
+
|
|
238
|
+
const key = fontName + ' ' + weight
|
|
239
|
+
if (this._checkedFonts.has(key)) return
|
|
240
|
+
this._checkedFonts.add(key)
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const font = this._availableFonts[key] ?? this._availableFonts[fontName] ?? await this._queryLocalFont(fontName, weight) ?? await this._queryRemoteFont(fontName, key)
|
|
244
|
+
if (font) return await this.addFonts([font])
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.warn('Error querying font', fontName, weight, e)
|
|
219
247
|
}
|
|
220
248
|
}
|
|
221
249
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this._wasm.addFont('font-' + (this._fontId++), ptr, uint8.byteLength)
|
|
227
|
-
this._wasm.reloadFonts()
|
|
250
|
+
queryFonts
|
|
251
|
+
async _queryLocalFont (fontName: string, weight: WeightValue) {
|
|
252
|
+
if (!this.queryFonts) return
|
|
253
|
+
return await this._getFont(fontName, weight)
|
|
228
254
|
}
|
|
229
255
|
|
|
230
|
-
|
|
231
|
-
if (
|
|
256
|
+
async _queryRemoteFont (fontName: string, postscriptName: string) {
|
|
257
|
+
if (this.queryFonts !== 'localandremote') return
|
|
232
258
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
259
|
+
const fontData = await queryRemoteFonts({ postscriptNames: [postscriptName, fontName] })
|
|
260
|
+
if (!fontData.length) return
|
|
261
|
+
const blob = await fontData[0]!.blob()
|
|
262
|
+
return new Uint8Array(await blob.arrayBuffer())
|
|
263
|
+
}
|
|
236
264
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
265
|
+
async _asyncWrite (font: string) {
|
|
266
|
+
const res = await _fetch(font)
|
|
267
|
+
this._allocFonts([new Uint8Array(await res.arrayBuffer())])
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_fontId = 0
|
|
271
|
+
_allocFonts (uint8s: Uint8Array[]) {
|
|
272
|
+
// TODO: this should re-draw last frame!
|
|
273
|
+
for (const uint8 of uint8s) {
|
|
274
|
+
const ptr = this._malloc(uint8.byteLength)
|
|
275
|
+
self.HEAPU8RAW.set(uint8, ptr)
|
|
276
|
+
this._wasm.addFont('font-' + (this._fontId++), ptr, uint8.byteLength)
|
|
241
277
|
}
|
|
278
|
+
this._wasm.reloadFonts()
|
|
242
279
|
}
|
|
243
280
|
|
|
244
281
|
_resizeCanvas (width: number, height: number, videoWidth: number, videoHeight: number) {
|
|
@@ -260,7 +297,7 @@ export class ASSRenderer {
|
|
|
260
297
|
_draw (time: number, repaint = false) {
|
|
261
298
|
if (!this._offCanvas || !this._gpurender) return
|
|
262
299
|
|
|
263
|
-
const result
|
|
300
|
+
const result = this._wasm.rawRender(time, Number(repaint))!
|
|
264
301
|
if (this._wasm.changed === 0 && !repaint) return
|
|
265
302
|
|
|
266
303
|
const bitmaps: ASSImage[] = []
|