jassub 1.7.1 → 1.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "libass Subtitle Renderer and Parser library for browsers",
5
5
  "main": "src/jassub.js",
6
6
  "type": "module",
@@ -29,14 +29,16 @@
29
29
  },
30
30
  "homepage": "https://github.com/ThaUnknown/jassub",
31
31
  "dependencies": {
32
- "rvfc-polyfill": "^1.0.4"
32
+ "rvfc-polyfill": "^1.0.6"
33
33
  },
34
34
  "devDependencies": {
35
- "vite": "^4.1.4",
36
- "vite-plugin-static-copy": "^0.13.1",
37
- "terser": "^5.17.3"
35
+ "terser": "^5.19.4",
36
+ "vite": "^4.4.9",
37
+ "vite-plugin-static-copy": "^0.17.0"
38
38
  },
39
39
  "scripts": {
40
- "build": "node vite.build.js"
40
+ "build": "node vite.build.js",
41
+ "docker:build": "docker build -t thaunknown/jassub-build .",
42
+ "docker:run": "docker run -it --rm -v ${PWD}:/code --name thaunknown_jassub-build thaunknown/jassub-build:latest"
41
43
  }
42
44
  }
package/src/jassub.js CHANGED
@@ -58,17 +58,16 @@ export default class JASSUB extends EventTarget {
58
58
  * @param {Number} [options.libassMemoryLimit] libass bitmap cache memory limit in MiB (approximate).
59
59
  * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate).
60
60
  */
61
- constructor (options = {}) {
61
+ constructor (options) {
62
62
  super()
63
- if (!globalThis.Worker) {
64
- this.destroy('Worker not supported')
65
- }
63
+ if (!globalThis.Worker) throw this.destroy('Worker not supported')
64
+ if (!options) throw this.destroy('No options provided')
66
65
 
67
- this._loaded = new Promise(resolve => {
66
+ this._loaded = /** @type {Promise<void>} */(new Promise(resolve => {
68
67
  this._init = resolve
69
- })
68
+ }))
70
69
 
71
- JASSUB._test()
70
+ const test = JASSUB._test()
72
71
  this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
73
72
 
74
73
  // don't support offscreen rendering on custom canvases, as we can't replace it if colorSpace doesn't match
@@ -85,7 +84,7 @@ export default class JASSUB extends EventTarget {
85
84
  this._canvasParent.className = 'JASSUB'
86
85
  this._canvasParent.style.position = 'relative'
87
86
 
88
- this._createCanvas()
87
+ this._canvas = this._createCanvas()
89
88
 
90
89
  if (this._video.nextSibling) {
91
90
  this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling)
@@ -93,11 +92,12 @@ export default class JASSUB extends EventTarget {
93
92
  this._video.parentNode.appendChild(this._canvasParent)
94
93
  }
95
94
  } else if (!this._canvas) {
96
- this.destroy('Don\'t know where to render: you should give video or canvas in options.')
95
+ throw this.destroy('Don\'t know where to render: you should give video or canvas in options.')
97
96
  }
98
97
 
99
98
  this._bufferCanvas = document.createElement('canvas')
100
99
  this._bufferCtx = this._bufferCanvas.getContext('2d')
100
+ if (!this._bufferCtx) throw this.destroy('Canvas rendering not supported')
101
101
 
102
102
  this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
103
103
  this._ctx = !this._offscreenRender && this._canvasctrl.getContext('2d')
@@ -124,29 +124,33 @@ export default class JASSUB extends EventTarget {
124
124
  this._worker.onmessage = e => this._onmessage(e)
125
125
  this._worker.onerror = e => this._error(e)
126
126
 
127
- this._worker.postMessage({
128
- target: 'init',
129
- wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl || 'jassub-worker.wasm',
130
- legacyWasmUrl: options.legacyWasmUrl || 'jassub-worker.wasm.js',
131
- asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
132
- onDemandRender: this._onDemandRender,
133
- width: this._canvasctrl.width || 0,
134
- height: this._canvasctrl.height || 0,
135
- blendMode: options.blendMode || 'js',
136
- subUrl: options.subUrl,
137
- subContent: options.subContent || null,
138
- fonts: options.fonts || [],
139
- availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
140
- fallbackFont: options.fallbackFont || 'liberation sans',
141
- debug: this.debug,
142
- targetFps: options.targetFps || 24,
143
- dropAllAnimations: options.dropAllAnimations,
144
- dropAllBlur: options.dropAllBlur,
145
- libassMemoryLimit: options.libassMemoryLimit || 0,
146
- libassGlyphLimit: options.libassGlyphLimit || 0,
147
- useLocalFonts: typeof queryLocalFonts !== 'undefined' && (options.useLocalFonts ?? true)
127
+ test.then(() => {
128
+ this._worker.postMessage({
129
+ target: 'init',
130
+ wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl || 'jassub-worker.wasm',
131
+ legacyWasmUrl: options.legacyWasmUrl || 'jassub-worker.wasm.js',
132
+ asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
133
+ onDemandRender: this._onDemandRender,
134
+ width: this._canvasctrl.width || 0,
135
+ height: this._canvasctrl.height || 0,
136
+ blendMode: options.blendMode || 'js',
137
+ subUrl: options.subUrl,
138
+ subContent: options.subContent || null,
139
+ fonts: options.fonts || [],
140
+ availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
141
+ fallbackFont: options.fallbackFont || 'liberation sans',
142
+ debug: this.debug,
143
+ targetFps: options.targetFps || 24,
144
+ dropAllAnimations: options.dropAllAnimations,
145
+ dropAllBlur: options.dropAllBlur,
146
+ libassMemoryLimit: options.libassMemoryLimit || 0,
147
+ libassGlyphLimit: options.libassGlyphLimit || 0,
148
+ // @ts-ignore
149
+ useLocalFonts: typeof queryLocalFonts !== 'undefined' && (options.useLocalFonts ?? true),
150
+ hasBitmapBug: JASSUB._hasBitmapBug
151
+ })
152
+ if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
148
153
  })
149
- if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
150
154
  }
151
155
 
152
156
  _createCanvas () {
@@ -155,15 +159,21 @@ export default class JASSUB extends EventTarget {
155
159
  this._canvas.style.position = 'absolute'
156
160
  this._canvas.style.pointerEvents = 'none'
157
161
  this._canvasParent.appendChild(this._canvas)
162
+ return this._canvas
158
163
  }
159
164
 
160
165
  // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
166
+
167
+ /** @type {boolean|null} */
161
168
  static _supportsSIMD = null
169
+ /** @type {boolean|null} */
162
170
  static _hasAlphaBug = null
171
+ /** @type {boolean|null} */
172
+ static _hasBitmapBug = null
163
173
 
164
- static _test () {
174
+ static async _test () {
165
175
  // check if ran previously
166
- if (JASSUB._supportsSIMD !== null) return null
176
+ if (JASSUB._hasBitmapBug !== null) return null
167
177
 
168
178
  try {
169
179
  JASSUB._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11))
@@ -173,6 +183,7 @@ export default class JASSUB extends EventTarget {
173
183
 
174
184
  const canvas1 = document.createElement('canvas')
175
185
  const ctx1 = canvas1.getContext('2d', { willReadFrequently: true })
186
+ if (!ctx1) throw new Error('Canvas rendering not supported')
176
187
  // test ImageData constructor
177
188
  if (typeof ImageData.prototype.constructor === 'function') {
178
189
  try {
@@ -183,6 +194,7 @@ export default class JASSUB extends EventTarget {
183
194
  } catch (e) {
184
195
  console.log('Detected that ImageData is not constructable despite browser saying so')
185
196
 
197
+ // @ts-ignore
186
198
  self.ImageData = function (data, width, height) {
187
199
  const imageData = ctx1.createImageData(width, height)
188
200
  if (data) imageData.data.set(data)
@@ -195,6 +207,7 @@ export default class JASSUB extends EventTarget {
195
207
  // (with alpha == 0) as non-black which then leads to visual artifacts.
196
208
  const canvas2 = document.createElement('canvas')
197
209
  const ctx2 = canvas2.getContext('2d', { willReadFrequently: true })
210
+ if (!ctx2) throw new Error('Canvas rendering not supported')
198
211
 
199
212
  canvas1.width = canvas2.width = 1
200
213
  canvas1.height = canvas2.height = 1
@@ -206,6 +219,24 @@ export default class JASSUB extends EventTarget {
206
219
  const postPut = ctx2.getImageData(0, 0, 1, 1).data
207
220
  JASSUB._hasAlphaBug = prePut[1] !== postPut[1]
208
221
  if (JASSUB._hasAlphaBug) console.log('Detected a browser having issue with transparent pixels, applying workaround')
222
+
223
+ if (typeof createImageBitmap !== 'undefined') {
224
+ const subarray = new Uint8ClampedArray([255, 0, 255, 0, 255]).subarray(1, 5)
225
+ ctx2.drawImage(await createImageBitmap(new ImageData(subarray, 1)), 0, 0)
226
+ const { data } = ctx2.getImageData(0, 0, 1, 1)
227
+ JASSUB._hasBitmapBug = false
228
+ for (const [i, number] of data.entries()) {
229
+ // realistically at most this will be a diff of 4, but just to be safe
230
+ if (Math.abs(subarray[i] - number) > 15) {
231
+ JASSUB._hasBitmapBug = true
232
+ console.log('Detected a browser having issue with partial bitmaps, applying workaround')
233
+ break
234
+ }
235
+ }
236
+ } else {
237
+ JASSUB._hasBitmapBug = false
238
+ }
239
+
209
240
  canvas1.remove()
210
241
  canvas2.remove()
211
242
  }
@@ -443,7 +474,7 @@ export default class JASSUB extends EventTarget {
443
474
 
444
475
  /**
445
476
  * Get all ASS events.
446
- * @param {function(Error|null, ASS_Event)} callback Function to callback when worker returns the events.
477
+ * @param {function(Error|null, ASS_Event): void} callback Function to callback when worker returns the events.
447
478
  */
448
479
  getEvents (callback) {
449
480
  this._fetchFromWorker({
@@ -485,7 +516,7 @@ export default class JASSUB extends EventTarget {
485
516
 
486
517
  /**
487
518
  * Create a new ASS style directly.
488
- * @param {ASS_Style} event
519
+ * @param {ASS_Style} style
489
520
  */
490
521
  createStyle (style) {
491
522
  this.sendMessage('createStyle', { style })
@@ -510,7 +541,7 @@ export default class JASSUB extends EventTarget {
510
541
 
511
542
  /**
512
543
  * Get all ASS styles.
513
- * @param {function(Error|null, ASS_Style)} callback Function to callback when worker returns the styles.
544
+ * @param {function(Error|null, ASS_Style): void} callback Function to callback when worker returns the styles.
514
545
  */
515
546
  getStyles (callback) {
516
547
  this._fetchFromWorker({
@@ -530,6 +561,7 @@ export default class JASSUB extends EventTarget {
530
561
 
531
562
  _sendLocalFont (name) {
532
563
  try {
564
+ // @ts-ignore
533
565
  queryLocalFonts().then(fontData => {
534
566
  const font = fontData?.find(obj => obj.fullName.toLowerCase() === name)
535
567
  if (font) {
@@ -550,6 +582,7 @@ export default class JASSUB extends EventTarget {
550
582
  // electron by default has all permissions enabled, and it doesn't have perm query
551
583
  // if this happens, just send it
552
584
  if (navigator?.permissions?.query) {
585
+ // @ts-ignore
553
586
  navigator.permissions.query({ name: 'local-fonts' }).then(permission => {
554
587
  if (permission.state === 'granted') {
555
588
  this._sendLocalFont(font)
@@ -634,8 +667,9 @@ export default class JASSUB extends EventTarget {
634
667
 
635
668
  /**
636
669
  * Veryify the color spaces for subtitles and videos, then apply filters to correct the color of subtitles.
637
- * @param {String} subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
638
- * @param {String} videoColorSpace Video color space. One of: BT601 BT709
670
+ * @param {Object} options
671
+ * @param {String} options.subtitleColorSpace Subtitle color space. One of: BT601 BT709 SMPTE240M FCC
672
+ * @param {String=} options.videoColorSpace Video color space. One of: BT601 BT709
639
673
  */
640
674
  _verifyColorSpace ({ subtitleColorSpace, videoColorSpace = this._videoColorSpace }) {
641
675
  if (!subtitleColorSpace || !videoColorSpace) return
@@ -767,6 +801,8 @@ export default class JASSUB extends EventTarget {
767
801
  this.dispatchEvent(event)
768
802
 
769
803
  console.error(error)
804
+
805
+ return error
770
806
  }
771
807
 
772
808
  _removeListeners () {
@@ -786,14 +822,15 @@ export default class JASSUB extends EventTarget {
786
822
 
787
823
  /**
788
824
  * Destroy the object, worker, listeners and all data.
789
- * @param {String} [err] Error to throw when destroying.
825
+ * @param {String|Error} [err] Error to throw when destroying.
790
826
  */
791
827
  destroy (err) {
792
- if (err) this._error(err)
793
- if (this._video && this._canvasParent) this._video.parentNode.removeChild(this._canvasParent)
828
+ if (err) err = this._error(err)
829
+ if (this._video && this._canvasParent) this._video.parentNode?.removeChild(this._canvasParent)
794
830
  this._destroyed = true
795
831
  this._removeListeners()
796
832
  this.sendMessage('destroy')
797
- this._worker.terminate()
833
+ this._worker?.terminate()
834
+ return err
798
835
  }
799
836
  }