jassub 1.5.13 → 1.6.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.
@@ -1 +1,2 @@
1
- (function(h,o){typeof exports=="object"&&typeof module<"u"?module.exports=o():typeof define=="function"&&define.amd?define(o):(h=typeof globalThis<"u"?globalThis:h||self,h.JASSUB=o())})(this,function(){"use strict";var u=Object.defineProperty;var f=(h,o,l)=>o in h?u(h,o,{enumerable:!0,configurable:!0,writable:!0,value:l}):h[o]=l;var v=(h,o,l)=>(f(h,typeof o!="symbol"?o+"":o,l),l);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(l){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(r,i)=>{const d=this.getVideoPlaybackQuality(),m=this.mozPresentedFrames||this.mozPaintedFrames||d.totalVideoFrames-d.droppedVideoFrames;if(m>t){const c=this.mozFrameDelay||d.totalFrameDelay-e.totalFrameDelay||0,_=i-r;l(i,{presentationTime:i+c*1e3,expectedDisplayTime:i+_,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+_/1e3,presentedFrames:m,processingDuration:c}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(c=>s(i,c))},a=Date.now(),n=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(r=>s(n,r)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(l){cancelAnimationFrame(this._rvfcpolyfillmap[l]),delete this._rvfcpolyfillmap[l]});const o=class extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),o._test();const t=e.blendMode||"js",s=typeof createImageBitmap<"u"&&(e.asyncRender??!0),a=typeof OffscreenCanvas<"u"&&(e.offscreenRender??!0);this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d",{desynchronized:!0,willReadFrequently:!0}),this._canvasctrl=a?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!a&&this._canvasctrl.getContext("2d",{desynchronized:!0}),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._worker=new Worker(o._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=n=>this._onmessage(n),this._worker.onerror=n=>this._error(n),this._loaded=new Promise(n=>{this._init=()=>{var r;this._destroyed||(this._worker.postMessage({target:"init",asyncRender:s,onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,preMain:!0,blendMode:t,subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:o._hasAlphaBug,useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),a===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null,(r=this._video)==null||r.requestVideoFrameCallback(this._handleRVFC.bind(this))),n())}})}static _test(){if(o._supportsWebAssembly!==null)return null;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(d,m,c){const _=t.createImageData(m,c);return d&&_.data.set(d),_}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const i=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));i instanceof WebAssembly.Module&&(o._supportsWebAssembly=new WebAssembly.Instance(i)instanceof WebAssembly.Instance)}}catch{o._supportsWebAssembly=!1}const s=document.createElement("canvas"),a=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const n=a.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(e,0,0);const r=a.getImageData(0,0,1,1).data;o._hasAlphaBug=n[1]!==r[1],o._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,a=0,n=(r=>(r=this._video)==null?void 0:r.paused)()){if((!e||!t)&&this._video){const i=this._getVideoPosition();let d=null;if(this._videoWidth){const m=this._video.videoWidth/this._videoWidth,c=this._video.videoHeight/this._videoHeight;d=this._computeCanvasSize((i.width||0)/m,(i.height||0)/c)}else d=this._computeCanvasSize(i.width||0,i.height||0);e=d.width,t=d.height,this._canvasParent&&(s=i.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=i.x),this._canvas.style.width=i.width+"px",this._canvas.style.height=i.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=a+"px",this.sendMessage("canvas",{width:e,height:t,force:n&&this.busy===!1})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:a,offsetHeight:n}=this._video,r=a/n;e=a,t=n,r>s?e=Math.floor(n*s):t=Math.floor(a/s);const i=(a-e)/2,d=(n-t)/2;return{width:e,height:t,x:i,y:d}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,a=self.devicePixelRatio||1;if(e=e*a,t=t*a,t<=0||e<=0)e=0,t=0;else{const n=s<1?-1:1;let r=t*a;n*r*s<=n*this.prescaleHeightLimit?r*=s:n*r<n*this.prescaleHeightLimit&&(r=this.prescaleHeightLimit),this.maxRenderHeight>0&&r>this.maxRenderHeight&&(r=this.maxRenderHeight),e*=r/t,t=r}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize)),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e})}setTrack(e){this.sendMessage("setTrack",{content:e})}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t==null?void 0:t.find(a=>a.fullName.toLowerCase()===e);s&&s.blob().then(a=>{a.arrayBuffer().then(n=>{this.addFont(new Uint8Array(n))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(t=navigator==null?void 0:navigator.permissions)!=null&&t.query?navigator.permissions.query({name:"local-fonts"}).then(s=>{s.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:a}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:a}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:a})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_render({images:e,async:t,times:s,width:a,height:n}){this._unbusy();const r=Date.now();(this._canvasctrl.width!==a||this._canvasctrl.height!==n)&&(this._canvasctrl.width=a,this._canvasctrl.height=n),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const i of e)i.image&&(t?(this._ctx.drawImage(i.image,i.x,i.y),i.image.close()):(this._bufferCanvas.width=i.w,this._bufferCanvas.height=i.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)),i.w,i.h),0,0),this._ctx.drawImage(this._bufferCanvas,i.x,i.y)));if(this.debug){s.drawTime=Date.now()-r;let i=0;for(const d in s)i+=s[d];console.log("Bitmaps: "+e.length+" Total: "+Math.round(i)+"ms",s)}}_fixAlpha(e){if(o._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,a=setTimeout(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),n=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a))},r=i=>{t(i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a)};this._worker.addEventListener("message",n),this._worker.addEventListener("error",r),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=o;return v(h,"_supportsWebAssembly",null),v(h,"_hasAlphaBug",null),h});
1
+ (function(d,l){typeof exports=="object"&&typeof module<"u"?module.exports=l():typeof define=="function"&&define.amd?define(l):(d=typeof globalThis<"u"?globalThis:d||self,d.JASSUB=l())})(this,function(){"use strict";var g=Object.defineProperty;var p=(d,l,h)=>l in d?g(d,l,{enumerable:!0,configurable:!0,writable:!0,value:h}):d[l]=h;var u=(d,l,h)=>(p(d,typeof l!="symbol"?l+"":l,h),h);!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(_){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,r)=>{const a=this.getVideoPlaybackQuality(),m=this.mozPresentedFrames||this.mozPaintedFrames||a.totalVideoFrames-a.droppedVideoFrames;if(m>t){const v=this.mozFrameDelay||a.totalFrameDelay-e.totalFrameDelay||0,f=r-n;_(r,{presentationTime:r+v*1e3,expectedDisplayTime:r+f,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+f/1e3,presentedFrames:m,processingDuration:v}),delete this._rvfcpolyfillmap[i]}else this._rvfcpolyfillmap[i]=requestAnimationFrame(v=>s(r,v))},i=Date.now(),o=performance.now();return this._rvfcpolyfillmap[i]=requestAnimationFrame(n=>s(o,n)),i},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(_){cancelAnimationFrame(this._rvfcpolyfillmap[_]),delete this._rvfcpolyfillmap[_]});const d={bt709:"BT.709",bt470bg:"BT.601",smpte170m:"BT.601"},l={"BT.601":{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418 0 0 0 0 0 1 0'/></filter></svg>#f")`},"BT.709":{"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582 0 0 0 0 0 1 0'/></filter></svg>#f")`},FCC:{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251
2
+ 1.0378 0 0 0 0 0 1 0'/></filter></svg>#f")`,"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996 0 0 0 0 0 1 0'/></filter></svg>#f")`},SMPTE240M:{"BT.709":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148 0 0 0 0 0 1 0'/></filter></svg>#f")`,"BT.601":`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973 0 0 0 0 0 1 0'/></filter></svg>#f")`}},c=class extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),c._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas),this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._worker=new Worker(c._supportsWebAssembly?e.workerUrl||"jassub-worker.js":e.legacyWorkerUrl||"jassub-worker-legacy.js"),this._worker.onmessage=t=>this._onmessage(t),this._worker.onerror=t=>this._error(t),this._loaded=new Promise(t=>{this._init=()=>{this._destroyed||(this._worker.postMessage({target:"init",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,preMain:!0,blendMode:e.blendMode||"js",subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,hasAlphaBug:c._hasAlphaBug,offscreenRender:typeof OffscreenCanvas<"u"&&(e.offscreenRender??!0),useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),t())}})}static _test(){if(c._supportsWebAssembly!==null)return null;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(a,m,v){const f=t.createImageData(m,v);return a&&f.data.set(a),f}}try{if(typeof WebAssembly=="object"&&typeof WebAssembly.instantiate=="function"){const r=new WebAssembly.Module(Uint8Array.of(0,97,115,109,1,0,0,0));r instanceof WebAssembly.Module&&(c._supportsWebAssembly=new WebAssembly.Instance(r)instanceof WebAssembly.Instance)}}catch{c._supportsWebAssembly=!1}const s=document.createElement("canvas"),i=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),i.clearRect(0,0,1,1);const o=i.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),i.drawImage(e,0,0);const n=i.getImageData(0,0,1,1).data;c._hasAlphaBug=o[1]!==n[1],c._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,i=0,o=(n=>(n=this._video)==null?void 0:n.paused)()){if((!e||!t)&&this._video){const r=this._getVideoPosition();let a=null;if(this._videoWidth){const m=this._video.videoWidth/this._videoWidth,v=this._video.videoHeight/this._videoHeight;a=this._computeCanvasSize((r.width||0)/m,(r.height||0)/v)}else a=this._computeCanvasSize(r.width||0,r.height||0);e=a.width,t=a.height,this._canvasParent&&(s=r.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=r.x),this._canvas.style.width=r.width+"px",this._canvas.style.height=r.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",this.sendMessage("canvas",{width:e,height:t,force:o&&this.busy===!1})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:o}=this._video,n=i/o;e=i,t=o,n>s?e=Math.floor(o*s):t=Math.floor(i/s);const r=(i-e)/2,a=(o-t)/2;return{width:e,height:t,x:r,y:a}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(e=e*i,t=t*i,t<=0||e<=0)e=0,t=0;else{const o=s<1?-1:1;let n=t*i;o*n*s<=o*this.prescaleHeightLimit?n*=s:o*n<o*this.prescaleHeightLimit&&(n=this.prescaleHeightLimit),this.maxRenderHeight>0&&n>this.maxRenderHeight&&(n=this.maxRenderHeight),e*=n/t,t=n}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{const e=new VideoFrame(this._video);this._videoColorSpace=d[e.colorSpace.matrix],e.close()})}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e})}setTrack(e){this.sendMessage("setTrack",{content:e})}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t==null?void 0:t.find(i=>i.fullName.toLowerCase()===e);s&&s.blob().then(i=>{i.arrayBuffer().then(o=>{this.addFont(new Uint8Array(o))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){var t;try{(t=navigator==null?void 0:navigator.permissions)!=null&&t.query?navigator.permissions.query({name:"local-fonts"}).then(s=>{s.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(s){console.warn("Local fonts API:",s)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}verifyColorSpace(e,t=this._videoColorSpace){!e||!t||e!==t&&(this._ctx.filter=l[e][t])}_render({images:e,async:t,times:s,width:i,height:o,colorSpace:n}){this._unbusy();const r=Date.now();(this._canvasctrl.width!==i||this._canvasctrl.height!==o)&&(this._canvasctrl.width=i,this._canvasctrl.height=o,this.verifyColorSpace(n)),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const a of e)a.image&&(t?(this._ctx.drawImage(a.image,a.x,a.y),a.image.close()):(this._bufferCanvas.width=a.w,this._bufferCanvas.height=a.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(a.image)),a.w,a.h),0,0),this._ctx.drawImage(this._bufferCanvas,a.x,a.y)));if(this.debug){s.drawTime=Date.now()-r;let a=0;for(const m in s)a+=s[m];console.log("Bitmaps: "+e.length+" Total: "+Math.round(a)+"ms",s)}}_fixAlpha(e){if(c._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{n(new Error("Error: Timeout while try to fetch "+s))},5e3),o=({data:r})=>{r.target===s&&(t(null,r),this._worker.removeEventListener("message",o),this._worker.removeEventListener("error",n),clearTimeout(i))},n=r=>{t(r),this._worker.removeEventListener("message",o),this._worker.removeEventListener("error",n),clearTimeout(i)};this._worker.addEventListener("message",o),this._worker.addEventListener("error",n),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx.filter="none",this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}};let h=c;return u(h,"_supportsWebAssembly",null),u(h,"_hasAlphaBug",null),h});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.5.13",
3
+ "version": "1.6.1",
4
4
  "description": "libass Subtitle Renderer and Parser library for browsers",
5
5
  "main": "src/jassub.js",
6
6
  "files": [
package/src/jassub.js CHANGED
@@ -1,5 +1,30 @@
1
1
  import 'rvfc-polyfill'
2
2
 
3
+ const webYCbCrMap = {
4
+ bt709: 'BT.709',
5
+ // these might not be exactly correct? oops?
6
+ bt470bg: 'BT.601', // alias BT.601 PAL... whats the difference?
7
+ smpte170m: 'BT.601'// alias BT.601 NTSC... whats the difference?
8
+ }
9
+
10
+ const colorMatrixConversionMap = {
11
+ 'BT.601': {
12
+ 'BT.709': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418 0 0 0 0 0 1 0\'/></filter></svg>#f")'
13
+ },
14
+ 'BT.709': {
15
+ 'BT.601': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582 0 0 0 0 0 1 0\'/></filter></svg>#f")'
16
+ },
17
+ FCC: {
18
+ 'BT.709': `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251
19
+ 1.0378 0 0 0 0 0 1 0'/></filter></svg>#f")`,
20
+ 'BT.601': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996 0 0 0 0 0 1 0\'/></filter></svg>#f")'
21
+ },
22
+ SMPTE240M: {
23
+ 'BT.709': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148 0 0 0 0 0 1 0\'/></filter></svg>#f")',
24
+ 'BT.601': 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'f\'><feColorMatrix type=\'matrix\' values=\'0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973 0 0 0 0 0 1 0\'/></filter></svg>#f")'
25
+ }
26
+ }
27
+
3
28
  /**
4
29
  * New JASSUB instance.
5
30
  * @class
@@ -37,15 +62,13 @@ export default class JASSUB extends EventTarget {
37
62
  this.destroy('Worker not supported')
38
63
  }
39
64
  JASSUB._test()
40
- const blendMode = options.blendMode || 'js'
41
- const asyncRender = typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true)
42
- const offscreenRender = typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true)
43
65
  this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
44
66
 
45
67
  this.timeOffset = options.timeOffset || 0
46
68
  this._video = options.video
47
69
  this._videoHeight = 0
48
70
  this._videoWidth = 0
71
+ this._videoColorSpace = null
49
72
  this._canvas = options.canvas
50
73
  if (this._video && !this._canvas) {
51
74
  this._canvasParent = document.createElement('div')
@@ -68,10 +91,10 @@ export default class JASSUB extends EventTarget {
68
91
  }
69
92
 
70
93
  this._bufferCanvas = document.createElement('canvas')
71
- this._bufferCtx = this._bufferCanvas.getContext('2d', { desynchronized: true, willReadFrequently: true })
94
+ this._bufferCtx = this._bufferCanvas.getContext('2d')
72
95
 
73
- this._canvasctrl = offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas
74
- this._ctx = !offscreenRender && this._canvasctrl.getContext('2d', { desynchronized: true })
96
+ this._canvasctrl = this._canvas
97
+ this._ctx = this._canvasctrl.getContext('2d')
75
98
 
76
99
  this._lastRenderTime = 0
77
100
  this.debug = !!options.debug
@@ -89,12 +112,12 @@ export default class JASSUB extends EventTarget {
89
112
  if (this._destroyed) return
90
113
  this._worker.postMessage({
91
114
  target: 'init',
92
- asyncRender,
115
+ asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
93
116
  onDemandRender: this._onDemandRender,
94
117
  width: this._canvasctrl.width || 0,
95
118
  height: this._canvasctrl.height || 0,
96
119
  preMain: true,
97
- blendMode,
120
+ blendMode: options.blendMode || 'js',
98
121
  subUrl: options.subUrl,
99
122
  subContent: options.subContent || null,
100
123
  fonts: options.fonts || [],
@@ -106,19 +129,19 @@ export default class JASSUB extends EventTarget {
106
129
  libassMemoryLimit: options.libassMemoryLimit || 0,
107
130
  libassGlyphLimit: options.libassGlyphLimit || 0,
108
131
  hasAlphaBug: JASSUB._hasAlphaBug,
132
+ offscreenRender: typeof OffscreenCanvas !== 'undefined' && (options.offscreenRender ?? true),
109
133
  useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
110
134
  })
111
- if (offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
112
135
 
113
136
  this._boundResize = this.resize.bind(this)
114
137
  this._boundTimeUpdate = this._timeupdate.bind(this)
115
138
  this._boundSetRate = this.setRate.bind(this)
139
+ this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
116
140
  if (this._video) this.setVideo(options.video)
117
141
 
118
142
  if (this._onDemandRender) {
119
143
  this.busy = false
120
144
  this._lastDemandTime = null
121
- this._video?.requestVideoFrameCallback(this._handleRVFC.bind(this))
122
145
  }
123
146
  resolve()
124
147
  }
@@ -272,6 +295,15 @@ export default class JASSUB extends EventTarget {
272
295
  this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset)
273
296
  }
274
297
 
298
+ _updateColorSpace () {
299
+ this._video.requestVideoFrameCallback(() => {
300
+ // eslint-disable-next-line no-undef
301
+ const frame = new VideoFrame(this._video)
302
+ this._videoColorSpace = webYCbCrMap[frame.colorSpace.matrix]
303
+ frame.close()
304
+ })
305
+ }
306
+
275
307
  /**
276
308
  * Change the video to use as target for event listeners.
277
309
  * @param {HTMLVideoElement} video
@@ -291,7 +323,12 @@ export default class JASSUB extends EventTarget {
291
323
  video.addEventListener('seeking', this._boundTimeUpdate, false)
292
324
  video.addEventListener('playing', this._boundTimeUpdate, false)
293
325
  video.addEventListener('ratechange', this._boundSetRate, false)
294
- video.addEventListener('resize', this._boundResize)
326
+ video.addEventListener('resize', this._boundResize, false)
327
+ }
328
+ // everything else is unreliable for this, loadedmetadata and loadeddata included.
329
+ if ('VideoFrame' in window) {
330
+ video.addEventListener('loadedmetadata', this._boundUpdateColorSpace, false)
331
+ if (video.readyState > 2) this._updateColorSpace()
295
332
  }
296
333
  if (video.videoWidth > 0) this.resize()
297
334
  // Support Element Resize Observer
@@ -550,12 +587,24 @@ export default class JASSUB extends EventTarget {
550
587
  this.sendMessage('demand', { time: mediaTime + this.timeOffset })
551
588
  }
552
589
 
553
- _render ({ images, async, times, width, height }) {
590
+ /**
591
+ * Veryify the color spaces for subtitles and videos, then apply filters to correct the color of subtitles.
592
+ * @param {String} subtitleColorSpace Subtitle color space. One of: BT.601 BT.709 SMPTE240M FCC
593
+ * @param {String} videoColorSpace Video color space. One of: BT.601 BT.709
594
+ */
595
+ verifyColorSpace (subtitleColorSpace, videoColorSpace = this._videoColorSpace) {
596
+ if (!subtitleColorSpace || !videoColorSpace) return
597
+ if (subtitleColorSpace === videoColorSpace) return
598
+ this._ctx.filter = colorMatrixConversionMap[subtitleColorSpace][videoColorSpace]
599
+ }
600
+
601
+ _render ({ images, async, times, width, height, colorSpace }) {
554
602
  this._unbusy()
555
603
  const drawStartTime = Date.now()
556
604
  if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
557
605
  this._canvasctrl.width = width
558
606
  this._canvasctrl.height = height
607
+ this.verifyColorSpace(colorSpace)
559
608
  }
560
609
  this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height)
561
610
  for (const image of images) {
@@ -675,6 +724,7 @@ export default class JASSUB extends EventTarget {
675
724
  _removeListeners () {
676
725
  if (this._video) {
677
726
  if (this._ro) this._ro.unobserve(this._video)
727
+ this._ctx.filter = 'none'
678
728
  this._video.removeEventListener('timeupdate', this._boundTimeUpdate)
679
729
  this._video.removeEventListener('progress', this._boundTimeUpdate)
680
730
  this._video.removeEventListener('waiting', this._boundTimeUpdate)
@@ -682,6 +732,7 @@ export default class JASSUB extends EventTarget {
682
732
  this._video.removeEventListener('playing', this._boundTimeUpdate)
683
733
  this._video.removeEventListener('ratechange', this._boundSetRate)
684
734
  this._video.removeEventListener('resize', this._boundResize)
735
+ this._video.removeEventListener('loadedmetadata', this._boundUpdateColorSpace)
685
736
  }
686
737
  }
687
738