Comparing version 0.2.1 to 0.2.2
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.libpgs=t():e.libpgs=t()}(self,(()=>(()=>{"use strict";var e={719:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DisplaySet=void 0;const n=i(498),s=i(866),r=i(818),o=i(207),a=i(32);t.DisplaySet=class{constructor(){this.presentationTimestamp=0,this.decodingTimestamp=0,this.paletteDefinitions=[],this.objectDefinitions=[],this.windowDefinitions=[]}read(e,t){for(this.presentationTimestamp=0,this.decodingTimestamp=0,this.presentationComposition=void 0,this.paletteDefinitions=[],this.objectDefinitions=[],this.windowDefinitions=[];;){if(t){if(20551!=e.readUInt16())throw new Error("Invalid magic number!");this.presentationTimestamp=e.readUInt32(),this.decodingTimestamp=e.readUInt32()}const i=e.readUInt8(),d=e.readUInt16();switch(i){case a.SegmentType.paletteDefinition:const t=new s.PaletteDefinitionSegment;t.read(e,d),this.paletteDefinitions.push(t);break;case a.SegmentType.objectDefinition:const h=new r.ObjectDefinitionSegment;h.read(e,d),this.objectDefinitions.push(h);break;case a.SegmentType.presentationComposition:const c=new n.PresentationCompositionSegment;c.read(e,d),this.presentationComposition=c;break;case a.SegmentType.windowDefinition:const p=new o.WindowDefinitionSegment;p.read(e,d),this.windowDefinitions.push(p);break;case a.SegmentType.end:return;default:throw new Error(`Unsupported segment type ${i}`)}}}}},818:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ObjectDefinitionSegment=void 0;const n=i(32);t.ObjectDefinitionSegment=class{constructor(){this.id=0,this.versionNumber=0,this.lastInSequenceFlag=0,this.width=0,this.height=0,this.dataLength=0}get isFirstInSequence(){return!!(128&this.lastInSequenceFlag)}get isLastInSequence(){return!!(64&this.lastInSequenceFlag)}get segmentType(){return n.SegmentType.objectDefinition}read(e,t){this.id=e.readUInt16(),this.versionNumber=e.readUInt8(),this.lastInSequenceFlag=e.readUInt8(),this.isFirstInSequence?(this.dataLength=e.readUInt24(),this.width=e.readUInt16(),this.height=e.readUInt16(),this.data=e.readBytes(t-11)):this.data=e.readBytes(t-4)}}},866:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PaletteDefinitionSegment=t.PaletteEntry=void 0;const n=i(32);class s{constructor(){this.y=0,this.cr=0,this.cb=0,this.r=0,this.g=0,this.b=0,this.a=0}}t.PaletteEntry=s;class r{constructor(){this.id=0,this.versionNumber=0,this.entries={}}get segmentType(){return n.SegmentType.paletteDefinition}read(e,t){this.id=e.readUInt8(),this.versionNumber=e.readUInt8();const i=(t-2)/5;this.entries={};for(let t=0;t<i;t++){const t=e.readUInt8(),i=new s;i.y=e.readUInt8(),i.cr=e.readUInt8(),i.cb=e.readUInt8(),i.a=e.readUInt8();const n=i.y,o=i.cb-128,a=i.cr-128;i.r=r.clamp(Math.round(n+1.402*a),0,255),i.g=r.clamp(Math.round(n-.34414*o-.71414*a),0,255),i.b=r.clamp(Math.round(n+1.772*o),0,255),this.entries[t]=i}}static clamp(e,t,i){return Math.max(t,Math.min(e,i))}}t.PaletteDefinitionSegment=r},498:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PresentationCompositionSegment=t.CompositionObject=void 0;const n=i(32);class s{constructor(){this.id=0,this.windowId=0,this.croppedFlag=0,this.horizontalPosition=0,this.verticalPosition=0,this.croppingHorizontalPosition=0,this.croppingVerticalPosition=0,this.croppingWidth=0,this.croppingHeightPosition=0}get hasCropping(){return!!(128&this.croppedFlag)}}t.CompositionObject=s,t.PresentationCompositionSegment=class{constructor(){this.width=0,this.height=0,this.frameRate=0,this.compositionNumber=0,this.compositionState=0,this.paletteUpdateFlag=0,this.paletteId=0,this.compositionObjects=[]}get segmentType(){return n.SegmentType.presentationComposition}read(e,t){this.width=e.readUInt16(),this.height=e.readUInt16(),this.frameRate=e.readUInt8(),this.compositionNumber=e.readUInt16(),this.compositionState=e.readUInt8(),this.paletteUpdateFlag=e.readUInt8(),this.paletteId=e.readUInt8();const i=e.readUInt8();this.compositionObjects=[];for(let t=0;t<i;t++){const t=new s;t.id=e.readUInt16(),t.windowId=e.readUInt8(),t.croppedFlag=e.readUInt8(),t.horizontalPosition=e.readUInt16(),t.verticalPosition=e.readUInt16(),t.hasCropping&&(t.croppingHorizontalPosition=e.readUInt16(),t.croppingVerticalPosition=e.readUInt16(),t.croppingWidth=e.readUInt16(),t.croppingHeightPosition=e.readUInt16()),this.compositionObjects.push(t)}}}},32:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.SegmentType=void 0,function(e){e[e.paletteDefinition=20]="paletteDefinition",e[e.objectDefinition=21]="objectDefinition",e[e.presentationComposition=22]="presentationComposition",e[e.windowDefinition=23]="windowDefinition",e[e.end=128]="end"}(i||(t.SegmentType=i={}))},207:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WindowDefinitionSegment=t.WindowDefinition=void 0;const n=i(32);class s{constructor(){this.id=0,this.horizontalPosition=0,this.verticalPosition=0,this.width=0,this.height=0}}t.WindowDefinition=s,t.WindowDefinitionSegment=class{constructor(){this.windows=[]}get segmentType(){return n.SegmentType.windowDefinition}read(e,t){const i=e.readUInt8();this.windows=[];for(let t=0;t<i;t++){const t=new s;t.id=e.readUInt8(),t.horizontalPosition=e.readUInt16(),t.verticalPosition=e.readUInt16(),t.width=e.readUInt16(),t.height=e.readUInt16(),this.windows.push(t)}}}},97:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))((function(s,r){function o(e){try{d(n.next(e))}catch(e){r(e)}}function a(e){try{d(n.throw(e))}catch(e){r(e)}}function d(e){var t;e.done?s(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(o,a)}d((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.PgsRenderer=void 0;const s=i(719),r=i(489),o=i(494),a=i(381);t.PgsRenderer=class{constructor(e){var t;if(this.displaySets=[],this.currentIndex=-1,this.onTimeUpdate=()=>{this.video&&this.renderAtTimestamp(this.video.currentTime+this._timeOffset)},this._timeOffset=0,e.video&&(this.video=e.video),e.canvas)this.canvas=e.canvas,this.canvasOwner=!1;else{if(!this.video)throw new Error("No canvas or video element was provided!");this.canvas=this.createCanvasElement(),this.canvasOwner=!0,this.video.parentElement.appendChild(this.canvas)}const i=this.canvas.getContext("2d");if(!i)throw new Error("Can not create 2d canvas context!");this.context=i,this._timeOffset=null!==(t=e.timeOffset)&&void 0!==t?t:0,this.registerEvents(),e.subUrl&&this.loadFromUrlAsync(e.subUrl).then()}createCanvasElement(){const e=document.createElement("canvas");return e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.right="0",e.style.bottom="0",e.style.pointerEvents="none",e.style.objectFit="contain",e.style.width="100%",e.style.height="100%",e}destroyCanvasElement(){this.canvas.remove()}registerEvents(){this.video&&this.video.addEventListener("timeupdate",this.onTimeUpdate)}unregisterEvents(){this.video&&this.video.removeEventListener("timeupdate",this.onTimeUpdate)}get timeOffset(){return this._timeOffset}set timeOffset(e){this._timeOffset!==e&&(this._timeOffset=e,this.onTimeUpdate())}loadFromUrlAsync(e){return n(this,void 0,void 0,(function*(){const t=yield fetch(e),i=yield t.arrayBuffer();this.loadFromBuffer(i)}))}loadFromBuffer(e){this.displaySets=[];const t=new r.BigEndianBinaryReader(new Uint8Array(e));for(;t.position<t.length;){const e=new s.DisplaySet;e.read(t,!0),this.displaySets.push(e)}}renderAtTimestamp(e){let t=-1;e=1e3*e*90;for(const i of this.displaySets){if(i.presentationTimestamp>e)break;t++}if(this.currentIndex==t)return;if(this.currentIndex=t,t<0)return;const i=this.displaySets[t];this.renderDisplaySet(i)}renderDisplaySet(e){if(e.presentationComposition){this.canvas.width=e.presentationComposition.width,this.canvas.height=e.presentationComposition.height;for(const t of e.presentationComposition.compositionObjects)this.renderComposition(e,t)}}renderComposition(e,t){if(!e.presentationComposition)return;let i=e.windowDefinitions.flatMap((e=>e.windows)).find((e=>e.id===t.windowId));if(!i)return;const n=this.getPixelDataFromComposition(e,t);n&&this.context.drawImage(n,i.horizontalPosition,i.verticalPosition)}getPixelDataFromComposition(e,t){if(!e.presentationComposition)return;let i=e.paletteDefinitions.find((t=>{var i;return t.id===(null===(i=e.presentationComposition)||void 0===i?void 0:i.paletteId)}));if(!i)return;let n=0,s=0;const r=[];for(const i of e.objectDefinitions)i.id==t.id&&(i.isFirstInSequence&&(n=i.width,s=i.height),i.data&&r.push(i.data));if(0==r.length)return;const d=new a.CombinedBinaryReader(r),h=document.createElement("canvas"),c=h.getContext("2d");h.width=n,h.height=s;const p=c.createImageData(n,s),l=p.data;return o.RunLengthEncoding.decode(d,((e,t,n,s)=>{const r=null==i?void 0:i.entries[s];r&&(l[4*e]=r.r,l[4*e+1]=r.g,l[4*e+2]=r.b,l[4*e+3]=r.a)})),c.putImageData(p,0,0),h}dispose(){this.unregisterEvents(),this.canvasOwner&&this.destroyCanvasElement(),this.displaySets=[]}}},385:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ArrayBinaryReader=void 0,t.ArrayBinaryReader=class{constructor(e){this._position=0,this.array=e}get position(){return this._position}get length(){return this.array.length}readByte(){return this.array[this._position++]}readBytes(e){const t=this.array.slice(this._position,this._position+e);return this._position+=e,t}}},489:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BigEndianBinaryReader=void 0;const n=i(385);t.BigEndianBinaryReader=class{constructor(e){e instanceof Uint8Array?this.reader=new n.ArrayBinaryReader(e):this.reader=e}get position(){return this.reader.position}get length(){return this.reader.length}readUInt8(){return this.reader.readByte()}readUInt16(){return(this.reader.readByte()<<8)+this.reader.readByte()}readUInt24(){return(this.reader.readByte()<<16)+(this.reader.readByte()<<8)+this.reader.readByte()}readUInt32(){return(this.reader.readByte()<<24)+(this.reader.readByte()<<16)+(this.reader.readByte()<<8)+this.reader.readByte()}readBytes(e){return this.reader.readBytes(e)}}},381:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CombinedBinaryReader=void 0;const n=i(385);t.CombinedBinaryReader=class{constructor(e){this._position=0,this.subReaderIndex=0,this.subReaders=e.map((e=>e instanceof Uint8Array?new n.ArrayBinaryReader(e):e));let t=0;for(const i of e)t+=i.length;this._length=t}get position(){return this._position}get length(){return this._length}readByte(){for(;this.subReaders[this.subReaderIndex].position>=this.subReaders[this.subReaderIndex].length;)this.subReaderIndex++;return this._position++,this.subReaders[this.subReaderIndex].readByte()}readBytes(e){const t=new Uint8Array(e);for(let i=0;i<e;i++)t[i]=this.readByte();return t}}},494:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RunLengthEncoding=void 0;const n=i(385);t.RunLengthEncoding=class{static decode(e,t){e instanceof Uint8Array&&(e=new n.ArrayBinaryReader(e));let i=0,s=0,r=0;for(;e.position<e.length;){const n=e.readByte();if(0!=n){t(r++,i++,s,n);continue}const o=e.readByte();if(0==o){i=0,s++;continue}const a=!!(128&o);let d=63&o;64&o&&(d=(d<<8)+e.readByte());const h=a?e.readByte():0;for(let e=0;e<d;e++)t(r++,i++,s,h)}}}}},t={};function i(n){var s=t[n];if(void 0!==s)return s.exports;var r=t[n]={exports:{}};return e[n].call(r.exports,r,r.exports,i),r.exports}var n={};return(()=>{var e=n;Object.defineProperty(e,"__esModule",{value:!0}),e.PgsRenderer=void 0;const t=i(97);Object.defineProperty(e,"PgsRenderer",{enumerable:!0,get:function(){return t.PgsRenderer}})})(),n})())); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.libpgs=t():e.libpgs=t()}(self,(()=>(()=>{"use strict";var e={719:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DisplaySet=void 0;const n=i(498),s=i(866),o=i(818),r=i(207),a=i(32);t.DisplaySet=class{constructor(){this.presentationTimestamp=0,this.decodingTimestamp=0,this.paletteDefinitions=[],this.objectDefinitions=[],this.windowDefinitions=[]}read(e,t){for(this.presentationTimestamp=0,this.decodingTimestamp=0,this.presentationComposition=void 0,this.paletteDefinitions=[],this.objectDefinitions=[],this.windowDefinitions=[];;){if(t){if(20551!=e.readUInt16())throw new Error("Invalid magic number!");this.presentationTimestamp=e.readUInt32(),this.decodingTimestamp=e.readUInt32()}const i=e.readUInt8(),d=e.readUInt16();switch(i){case a.SegmentType.paletteDefinition:const t=new s.PaletteDefinitionSegment;t.read(e,d),this.paletteDefinitions.push(t);break;case a.SegmentType.objectDefinition:const h=new o.ObjectDefinitionSegment;h.read(e,d),this.objectDefinitions.push(h);break;case a.SegmentType.presentationComposition:const c=new n.PresentationCompositionSegment;c.read(e,d),this.presentationComposition=c;break;case a.SegmentType.windowDefinition:const p=new r.WindowDefinitionSegment;p.read(e,d),this.windowDefinitions.push(p);break;case a.SegmentType.end:return;default:throw new Error(`Unsupported segment type ${i}`)}}}}},818:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ObjectDefinitionSegment=void 0;const n=i(32);t.ObjectDefinitionSegment=class{constructor(){this.id=0,this.versionNumber=0,this.lastInSequenceFlag=0,this.width=0,this.height=0,this.dataLength=0}get isFirstInSequence(){return!!(128&this.lastInSequenceFlag)}get isLastInSequence(){return!!(64&this.lastInSequenceFlag)}get segmentType(){return n.SegmentType.objectDefinition}read(e,t){this.id=e.readUInt16(),this.versionNumber=e.readUInt8(),this.lastInSequenceFlag=e.readUInt8(),this.isFirstInSequence?(this.dataLength=e.readUInt24(),this.width=e.readUInt16(),this.height=e.readUInt16(),this.data=e.readBytes(t-11)):this.data=e.readBytes(t-4)}}},866:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PaletteDefinitionSegment=t.PaletteEntry=void 0;const n=i(32);class s{constructor(){this.y=0,this.cr=0,this.cb=0,this.r=0,this.g=0,this.b=0,this.a=0}}t.PaletteEntry=s;class o{constructor(){this.id=0,this.versionNumber=0,this.entries={}}get segmentType(){return n.SegmentType.paletteDefinition}read(e,t){this.id=e.readUInt8(),this.versionNumber=e.readUInt8();const i=(t-2)/5;this.entries={};for(let t=0;t<i;t++){const t=e.readUInt8(),i=new s;i.y=e.readUInt8(),i.cr=e.readUInt8(),i.cb=e.readUInt8(),i.a=e.readUInt8();const n=i.y,r=i.cb-128,a=i.cr-128;i.r=o.clamp(Math.round(n+1.402*a),0,255),i.g=o.clamp(Math.round(n-.34414*r-.71414*a),0,255),i.b=o.clamp(Math.round(n+1.772*r),0,255),this.entries[t]=i}}static clamp(e,t,i){return Math.max(t,Math.min(e,i))}}t.PaletteDefinitionSegment=o},498:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PresentationCompositionSegment=t.CompositionObject=void 0;const n=i(32);class s{constructor(){this.id=0,this.windowId=0,this.croppedFlag=0,this.horizontalPosition=0,this.verticalPosition=0,this.croppingHorizontalPosition=0,this.croppingVerticalPosition=0,this.croppingWidth=0,this.croppingHeightPosition=0}get hasCropping(){return!!(128&this.croppedFlag)}}t.CompositionObject=s,t.PresentationCompositionSegment=class{constructor(){this.width=0,this.height=0,this.frameRate=0,this.compositionNumber=0,this.compositionState=0,this.paletteUpdateFlag=0,this.paletteId=0,this.compositionObjects=[]}get segmentType(){return n.SegmentType.presentationComposition}read(e,t){this.width=e.readUInt16(),this.height=e.readUInt16(),this.frameRate=e.readUInt8(),this.compositionNumber=e.readUInt16(),this.compositionState=e.readUInt8(),this.paletteUpdateFlag=e.readUInt8(),this.paletteId=e.readUInt8();const i=e.readUInt8();this.compositionObjects=[];for(let t=0;t<i;t++){const t=new s;t.id=e.readUInt16(),t.windowId=e.readUInt8(),t.croppedFlag=e.readUInt8(),t.horizontalPosition=e.readUInt16(),t.verticalPosition=e.readUInt16(),t.hasCropping&&(t.croppingHorizontalPosition=e.readUInt16(),t.croppingVerticalPosition=e.readUInt16(),t.croppingWidth=e.readUInt16(),t.croppingHeightPosition=e.readUInt16()),this.compositionObjects.push(t)}}}},32:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.SegmentType=void 0,function(e){e[e.paletteDefinition=20]="paletteDefinition",e[e.objectDefinition=21]="objectDefinition",e[e.presentationComposition=22]="presentationComposition",e[e.windowDefinition=23]="windowDefinition",e[e.end=128]="end"}(i||(t.SegmentType=i={}))},207:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WindowDefinitionSegment=t.WindowDefinition=void 0;const n=i(32);class s{constructor(){this.id=0,this.horizontalPosition=0,this.verticalPosition=0,this.width=0,this.height=0}}t.WindowDefinition=s,t.WindowDefinitionSegment=class{constructor(){this.windows=[]}get segmentType(){return n.SegmentType.windowDefinition}read(e,t){const i=e.readUInt8();this.windows=[];for(let t=0;t<i;t++){const t=new s;t.id=e.readUInt8(),t.horizontalPosition=e.readUInt16(),t.verticalPosition=e.readUInt16(),t.width=e.readUInt16(),t.height=e.readUInt16(),this.windows.push(t)}}}},97:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){return new(i||(i=Promise))((function(s,o){function r(e){try{d(n.next(e))}catch(e){o(e)}}function a(e){try{d(n.throw(e))}catch(e){o(e)}}function d(e){var t;e.done?s(e.value):(t=e.value,t instanceof i?t:new i((function(e){e(t)}))).then(r,a)}d((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.PgsRenderer=void 0;const s=i(719),o=i(489),r=i(494),a=i(381);t.PgsRenderer=class{constructor(e){var t;if(this.$timeOffset=0,this.onTimeUpdate=()=>{this.renderAtVideoTimestamp()},this.displaySets=[],this.displaySetIndex=-1,e.video&&(this.video=e.video),e.canvas)this.canvas=e.canvas,this.canvasOwner=!1;else{if(!this.video)throw new Error("No canvas or video element was provided!");this.canvas=this.createCanvasElement(),this.canvasOwner=!0,this.video.parentElement.appendChild(this.canvas)}const i=this.canvas.getContext("2d");if(!i)throw new Error("Can not create 2d canvas context!");this.context=i,this.$timeOffset=null!==(t=e.timeOffset)&&void 0!==t?t:0,e.subUrl&&this.loadFromUrlAsync(e.subUrl).then(),this.registerVideoEvents()}createCanvasElement(){const e=document.createElement("canvas");return e.style.position="absolute",e.style.top="0",e.style.left="0",e.style.right="0",e.style.bottom="0",e.style.pointerEvents="none",e.style.objectFit="contain",e.style.width="100%",e.style.height="100%",e}destroyCanvasElement(){this.canvas.remove()}get timeOffset(){return this.$timeOffset}set timeOffset(e){this.$timeOffset!==e&&(this.$timeOffset=e,this.renderAtVideoTimestamp())}registerVideoEvents(){this.video&&this.video.addEventListener("timeupdate",this.onTimeUpdate)}unregisterVideoEvents(){this.video&&this.video.removeEventListener("timeupdate",this.onTimeUpdate)}renderAtVideoTimestamp(){this.video&&this.renderAtTimestamp(this.video.currentTime+this.$timeOffset)}loadFromUrlAsync(e){return n(this,void 0,void 0,(function*(){const t=yield fetch(e),i=yield t.arrayBuffer();this.loadFromBuffer(i)}))}loadFromBuffer(e){this.displaySets=[];const t=new o.BigEndianBinaryReader(new Uint8Array(e));for(;t.position<t.length;){const e=new s.DisplaySet;e.read(t,!0),this.displaySets.push(e)}this.renderAtVideoTimestamp()}renderAtTimestamp(e){e=1e3*e*90;let t=-1;for(const i of this.displaySets){if(i.presentationTimestamp>e)break;t++}if(this.displaySetIndex==t)return;if(this.displaySetIndex=t,t<0)return;const i=this.displaySets[t];this.renderDisplaySet(i)}renderDisplaySet(e){if(e.presentationComposition){this.canvas.width=e.presentationComposition.width,this.canvas.height=e.presentationComposition.height;for(const t of e.presentationComposition.compositionObjects)this.renderDisplaySetComposition(e,t)}}renderDisplaySetComposition(e,t){if(!e.presentationComposition)return;let i=e.windowDefinitions.flatMap((e=>e.windows)).find((e=>e.id===t.windowId));if(!i)return;const n=this.getPixelDataFromDisplaySetComposition(e,t);n&&this.context.drawImage(n,i.horizontalPosition,i.verticalPosition)}getPixelDataFromDisplaySetComposition(e,t){if(!e.presentationComposition)return;let i=e.paletteDefinitions.find((t=>{var i;return t.id===(null===(i=e.presentationComposition)||void 0===i?void 0:i.paletteId)}));if(!i)return;let n=0,s=0;const o=[];for(const i of e.objectDefinitions)i.id==t.id&&(i.isFirstInSequence&&(n=i.width,s=i.height),i.data&&o.push(i.data));if(0==o.length)return;const d=new a.CombinedBinaryReader(o),h=document.createElement("canvas"),c=h.getContext("2d");h.width=n,h.height=s;const p=c.createImageData(n,s),l=p.data;return r.RunLengthEncoding.decode(d,((e,t,n,s)=>{const o=null==i?void 0:i.entries[s];o&&(l[4*e]=o.r,l[4*e+1]=o.g,l[4*e+2]=o.b,l[4*e+3]=o.a)})),c.putImageData(p,0,0),h}dispose(){this.unregisterVideoEvents(),this.canvasOwner&&this.destroyCanvasElement(),this.displaySets=[]}}},385:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ArrayBinaryReader=void 0,t.ArrayBinaryReader=class{constructor(e){this.$position=0,this.array=e}get position(){return this.$position}get length(){return this.array.length}readByte(){return this.array[this.$position++]}readBytes(e){const t=this.array.slice(this.$position,this.$position+e);return this.$position+=e,t}}},489:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BigEndianBinaryReader=void 0;const n=i(385);t.BigEndianBinaryReader=class{constructor(e){e instanceof Uint8Array?this.reader=new n.ArrayBinaryReader(e):this.reader=e}get position(){return this.reader.position}get length(){return this.reader.length}readUInt8(){return this.reader.readByte()}readUInt16(){return(this.reader.readByte()<<8)+this.reader.readByte()}readUInt24(){return(this.reader.readByte()<<16)+(this.reader.readByte()<<8)+this.reader.readByte()}readUInt32(){return(this.reader.readByte()<<24)+(this.reader.readByte()<<16)+(this.reader.readByte()<<8)+this.reader.readByte()}readBytes(e){return this.reader.readBytes(e)}}},381:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CombinedBinaryReader=void 0;const n=i(385);t.CombinedBinaryReader=class{constructor(e){this.$position=0,this.subReaderIndex=0,this.subReaders=e.map((e=>e instanceof Uint8Array?new n.ArrayBinaryReader(e):e));let t=0;for(const i of e)t+=i.length;this.$length=t}get position(){return this.$position}get length(){return this.$length}readByte(){for(;this.subReaders[this.subReaderIndex].position>=this.subReaders[this.subReaderIndex].length;)this.subReaderIndex++;return this.$position++,this.subReaders[this.subReaderIndex].readByte()}readBytes(e){const t=new Uint8Array(e);for(let i=0;i<e;i++)t[i]=this.readByte();return t}}},494:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RunLengthEncoding=void 0;const n=i(385);t.RunLengthEncoding=class{static decode(e,t){e instanceof Uint8Array&&(e=new n.ArrayBinaryReader(e));let i=0,s=0,o=0;for(;e.position<e.length;){const n=e.readByte();if(0!=n){t(o++,i++,s,n);continue}const r=e.readByte();if(0==r){i=0,s++;continue}const a=!!(128&r);let d=63&r;64&r&&(d=(d<<8)+e.readByte());const h=a?e.readByte():0;for(let e=0;e<d;e++)t(o++,i++,s,h)}}}}},t={};function i(n){var s=t[n];if(void 0!==s)return s.exports;var o=t[n]={exports:{}};return e[n].call(o.exports,o,o.exports,i),o.exports}var n={};return(()=>{var e=n;Object.defineProperty(e,"__esModule",{value:!0}),e.PgsRenderer=void 0;const t=i(97);Object.defineProperty(e,"PgsRenderer",{enumerable:!0,get:function(){return t.PgsRenderer}})})(),n})())); |
@@ -6,2 +6,5 @@ import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader"; | ||
import { WindowDefinitionSegment } from "./windowDefinitionSegment"; | ||
/** | ||
* The PGS display set holds all data for the current subtitle update at a given timestamp. | ||
*/ | ||
export declare class DisplaySet { | ||
@@ -14,3 +17,9 @@ presentationTimestamp: number; | ||
windowDefinitions: WindowDefinitionSegment[]; | ||
/** | ||
* Reads a display set from the given binary reader. The current data is cleared. | ||
* @param reader The binary reader to read from. | ||
* @param includeHeader If true, the magic-number and timestamps are read. If false, reading starts at the first | ||
* segment. | ||
*/ | ||
read(reader: BigEndianBinaryReader, includeHeader: boolean): void; | ||
} |
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader"; | ||
export interface Segment { | ||
/** | ||
* Gets the {@link SegmentType} identifier byte. | ||
*/ | ||
get segmentType(): number; | ||
/** | ||
* Reads the segment from the data stream. | ||
* @param reader The binary reader to read from. | ||
* @param length The length of the segment in bytes. | ||
*/ | ||
read(reader: BigEndianBinaryReader, length: number): void; | ||
} |
import { PgsRendererOptions } from "./pgsRendererOptions"; | ||
/** | ||
* Renders PGS subtitle on-top of a video element using a canvas element. | ||
* Renders PGS subtitle on-top of a video element using a canvas element. This also handles timestamp updates if a | ||
* video element is provided. | ||
*/ | ||
export declare class PgsRenderer { | ||
/** | ||
* Creates and starts a PGS subtitle render with the given option. | ||
* @param options The PGS renderer options. | ||
*/ | ||
constructor(options: PgsRendererOptions); | ||
private readonly canvas; | ||
private readonly canvasOwner; | ||
private readonly context; | ||
private readonly video?; | ||
private displaySets; | ||
private currentIndex; | ||
constructor(options: PgsRendererOptions); | ||
private createCanvasElement; | ||
private destroyCanvasElement; | ||
private registerEvents; | ||
private unregisterEvents; | ||
private onTimeUpdate; | ||
private _timeOffset; | ||
private readonly video?; | ||
private $timeOffset; | ||
/** | ||
@@ -28,2 +28,8 @@ * Gets the video-to-subtitle time offset in seconds. | ||
set timeOffset(timeOffset: number); | ||
private registerVideoEvents; | ||
private unregisterVideoEvents; | ||
private onTimeUpdate; | ||
private renderAtVideoTimestamp; | ||
private displaySets; | ||
private displaySetIndex; | ||
/** | ||
@@ -45,4 +51,4 @@ * Loads the subtitle file from the given url. | ||
private renderDisplaySet; | ||
private renderComposition; | ||
private getPixelDataFromComposition; | ||
private renderDisplaySetComposition; | ||
private getPixelDataFromDisplaySetComposition; | ||
/** | ||
@@ -49,0 +55,0 @@ * Destroys the subtitle canvas and removes event listeners. |
import { BinaryReader } from "./binaryReader"; | ||
/** | ||
* A binary reader based on a {@link Uint8Array}. | ||
*/ | ||
export declare class ArrayBinaryReader implements BinaryReader { | ||
private readonly array; | ||
private _position; | ||
private $position; | ||
constructor(array: Uint8Array); | ||
@@ -6,0 +9,0 @@ get position(): number; |
import { BinaryReader } from "./binaryReader"; | ||
/** | ||
* A binary reader that combines multiple binary readers in one data stream. | ||
*/ | ||
export declare class CombinedBinaryReader implements BinaryReader { | ||
private readonly subReaders; | ||
private readonly _length; | ||
private _position; | ||
private readonly $length; | ||
private $position; | ||
private subReaderIndex; | ||
@@ -7,0 +10,0 @@ constructor(subReaders: BinaryReader[] | Uint8Array[]); |
import { BinaryReader } from "./binaryReader"; | ||
/** | ||
* Handles run length encoded images. | ||
*/ | ||
export declare abstract class RunLengthEncoding { | ||
@@ -3,0 +6,0 @@ /** |
{ | ||
"name": "libpgs", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"author": "David Schulte", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -5,2 +5,14 @@ # libpgs-js | ||
## Work in progress | ||
This project is still in progress. It should be able to play 99% of Blue ray subtitles _(Yes, I made that number up)_. | ||
But some rare used PGS features - like cropping - aren't implemented yet. | ||
- [x] Basic PGS rendering. | ||
- [x] Auto syncing to video element. | ||
- [x] Custom subtitle time offset. | ||
- [ ] Support subtitle cropping. | ||
- If you know a movie or show that is using the cropping feature, please let me know! | ||
- [ ] Improve performance by using a WebWorker to render. | ||
## Usage | ||
@@ -13,2 +25,30 @@ | ||
### Create with default canvas | ||
The PGS renderer will create a default canvas element next to the video element: | ||
```javascript | ||
const videoElement = document.getElementById('video-element'); | ||
const pgsRenderer = new libpgs.PgsRenderer({ | ||
video: videoElement, | ||
subUrl: './subtitle.sup' | ||
}); | ||
``` | ||
The created default canvas element is using a style definition like this: | ||
```css | ||
position: absolute; | ||
left: 0; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
width: '100%'; | ||
height: '100%'; | ||
pointer-events: 'none'; | ||
object-fit: 'contain'; | ||
``` | ||
This only works if the video element is stretched in its parent and if the parent is using the css `position` property. | ||
```html | ||
@@ -20,6 +60,12 @@ <div style="position: relative"> | ||
### Create with custom canvas | ||
It is also possible to provide a custom canvas element and position it manually: | ||
```javascript | ||
const videoElement = document.getElementById('video-element'); | ||
const canvasElement = document.getElementById('canvas-element'); | ||
const pgsRenderer = new libpgs.PgsRenderer({ | ||
video: videoElement, | ||
canvas: canvasElement, | ||
subUrl: './subtitle.sup' | ||
@@ -29,4 +75,22 @@ }); | ||
### Time offset | ||
You can also adjust time offset between video and subtitle: | ||
```javascript | ||
// Rendering the subtitle 3 seconds in advance of the video | ||
pgsRenderer.timeOffset = 3.0; | ||
``` | ||
### Destroy | ||
Make sure to dispose the renderer when leaving: | ||
```javascript | ||
// Releases video events and removes the default canvas element | ||
pgsRenderer.dispose(); | ||
``` | ||
## Licence | ||
[MIT License](LICENSE) |
@@ -8,2 +8,5 @@ import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader"; | ||
/** | ||
* The PGS display set holds all data for the current subtitle update at a given timestamp. | ||
*/ | ||
export class DisplaySet { | ||
@@ -17,2 +20,8 @@ public presentationTimestamp: number = 0; | ||
/** | ||
* Reads a display set from the given binary reader. The current data is cleared. | ||
* @param reader The binary reader to read from. | ||
* @param includeHeader If true, the magic-number and timestamps are read. If false, reading starts at the first | ||
* segment. | ||
*/ | ||
public read(reader: BigEndianBinaryReader, includeHeader: boolean) { | ||
@@ -19,0 +28,0 @@ |
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader"; | ||
export interface Segment { | ||
/// Gets the segment type | ||
/** | ||
* Gets the {@link SegmentType} identifier byte. | ||
*/ | ||
get segmentType() : number; | ||
/// Reads the segment from the data stream | ||
/** | ||
* Reads the segment from the data stream. | ||
* @param reader The binary reader to read from. | ||
* @param length The length of the segment in bytes. | ||
*/ | ||
read(reader: BigEndianBinaryReader, length: number): void; | ||
} |
@@ -9,12 +9,11 @@ import {DisplaySet} from "./pgs/displaySet"; | ||
/** | ||
* Renders PGS subtitle on-top of a video element using a canvas element. | ||
* Renders PGS subtitle on-top of a video element using a canvas element. This also handles timestamp updates if a | ||
* video element is provided. | ||
*/ | ||
export class PgsRenderer { | ||
private readonly canvas: HTMLCanvasElement; | ||
private readonly canvasOwner: boolean; | ||
private readonly context: CanvasRenderingContext2D; | ||
private readonly video?: HTMLVideoElement; | ||
private displaySets: DisplaySet[] = []; | ||
private currentIndex: number = -1; | ||
/** | ||
* Creates and starts a PGS subtitle render with the given option. | ||
* @param options The PGS renderer options. | ||
*/ | ||
public constructor(options: PgsRendererOptions) { | ||
@@ -26,5 +25,7 @@ if (options.video) { | ||
if (options.canvas) { | ||
// Use a canvas provided by the user | ||
this.canvas = options.canvas; | ||
this.canvasOwner = false; | ||
} else if (this.video) { | ||
// Create a new canvas next to the video element | ||
this.canvas = this.createCanvasElement(); | ||
@@ -43,12 +44,17 @@ this.canvasOwner = true; | ||
this._timeOffset = options.timeOffset ?? 0; | ||
this.registerEvents(); | ||
// Load the initial subtitle file | ||
// Load initial settings | ||
this.$timeOffset = options.timeOffset ?? 0; | ||
if (options.subUrl) { | ||
this.loadFromUrlAsync(options.subUrl).then(); | ||
} | ||
this.registerVideoEvents(); | ||
} | ||
// region Canvas | ||
private readonly canvas: HTMLCanvasElement; | ||
private readonly canvasOwner: boolean; | ||
private readonly context: CanvasRenderingContext2D; | ||
private createCanvasElement(): HTMLCanvasElement { | ||
@@ -72,3 +78,28 @@ const canvas = document.createElement('canvas'); | ||
private registerEvents(): void { | ||
// endregion | ||
// region Video | ||
private readonly video?: HTMLVideoElement; | ||
private $timeOffset: number = 0; | ||
/** | ||
* Gets the video-to-subtitle time offset in seconds. | ||
*/ | ||
public get timeOffset(): number { | ||
return this.$timeOffset; | ||
} | ||
/** | ||
* Sets the video-to-subtitle time offset and re-renders the current subtitle if needed. | ||
* @param timeOffset The new time offset in seconds. | ||
*/ | ||
public set timeOffset(timeOffset: number) { | ||
if (this.$timeOffset === timeOffset) return; | ||
this.$timeOffset = timeOffset; | ||
this.renderAtVideoTimestamp(); | ||
} | ||
private registerVideoEvents(): void { | ||
if (this.video) { | ||
@@ -79,3 +110,3 @@ this.video.addEventListener('timeupdate', this.onTimeUpdate); | ||
private unregisterEvents(): void { | ||
private unregisterVideoEvents(): void { | ||
if (this.video) { | ||
@@ -87,25 +118,17 @@ this.video.removeEventListener('timeupdate', this.onTimeUpdate); | ||
private onTimeUpdate = (): void => { | ||
this.renderAtVideoTimestamp(); | ||
} | ||
private renderAtVideoTimestamp() { | ||
if (this.video) { | ||
this.renderAtTimestamp(this.video.currentTime + this._timeOffset); | ||
this.renderAtTimestamp(this.video.currentTime + this.$timeOffset); | ||
} | ||
} | ||
private _timeOffset: number = 0; | ||
// endregion | ||
/** | ||
* Gets the video-to-subtitle time offset in seconds. | ||
*/ | ||
public get timeOffset(): number { | ||
return this._timeOffset; | ||
} | ||
// region Subtitle | ||
/** | ||
* Sets the video-to-subtitle time offset and re-renders the current subtitle if needed. | ||
* @param timeOffset The new time offset in seconds. | ||
*/ | ||
public set timeOffset(timeOffset: number) { | ||
if (this._timeOffset === timeOffset) return; | ||
this._timeOffset = timeOffset; | ||
this.onTimeUpdate(); | ||
} | ||
private displaySets: DisplaySet[] = []; | ||
private displaySetIndex: number = -1; | ||
@@ -134,4 +157,10 @@ /** | ||
} | ||
this.renderAtVideoTimestamp(); | ||
} | ||
// endregion | ||
// region Rendering | ||
/** | ||
@@ -142,4 +171,6 @@ * Renders the subtitle for the given timestamp. | ||
public renderAtTimestamp(time: number): void { | ||
time = time * 1000 * 90; // Convert to PGS time | ||
// Find the last display set index for the given time stamp | ||
let index = -1; | ||
time = time * 1000 * 90; // Convert to PGS time | ||
for (const displaySet of this.displaySets) { | ||
@@ -152,4 +183,5 @@ | ||
} | ||
if (this.currentIndex == index) return; | ||
this.currentIndex = index; | ||
// No need to update | ||
if (this.displaySetIndex == index) return; | ||
this.displaySetIndex = index; | ||
@@ -164,3 +196,3 @@ if (index < 0) return; | ||
// Setting the width and height will also clear the canvas | ||
// Setting the width and height will also clear the canvas. | ||
this.canvas.width = displaySet.presentationComposition.width; | ||
@@ -170,13 +202,14 @@ this.canvas.height = displaySet.presentationComposition.height; | ||
for (const composition of displaySet.presentationComposition.compositionObjects) { | ||
this.renderComposition(displaySet, composition); | ||
this.renderDisplaySetComposition(displaySet, composition); | ||
} | ||
} | ||
private renderComposition(displaySet: DisplaySet, composition: CompositionObject): void { | ||
private renderDisplaySetComposition(displaySet: DisplaySet, composition: CompositionObject): void { | ||
if (!displaySet.presentationComposition) return; | ||
let window = displaySet.windowDefinitions | ||
.flatMap(w => w.windows).find(w => w.id === composition.windowId); | ||
.flatMap(w => w.windows) | ||
.find(w => w.id === composition.windowId); | ||
if (!window) return; | ||
const pixelData = this.getPixelDataFromComposition(displaySet, composition); | ||
const pixelData = this.getPixelDataFromDisplaySetComposition(displaySet, composition); | ||
if (pixelData) { | ||
@@ -187,3 +220,4 @@ this.context.drawImage(pixelData, window.horizontalPosition, window.verticalPosition); | ||
private getPixelDataFromComposition(displaySet: DisplaySet, composition: CompositionObject): HTMLCanvasElement | undefined { | ||
private getPixelDataFromDisplaySetComposition(displaySet: DisplaySet, composition: CompositionObject): | ||
HTMLCanvasElement | undefined { | ||
if (!displaySet.presentationComposition) return undefined; | ||
@@ -194,3 +228,4 @@ let palette = displaySet.paletteDefinitions | ||
// Collect meta data | ||
// Multiple object definition can define a single subtitle image. | ||
// However, only the first element in sequence hold the image size. | ||
let width: number = 0; | ||
@@ -214,5 +249,7 @@ let height: number = 0; | ||
// Using a combined reader instead of stitching the data together. | ||
// This hopefully avoids a larger memory allocation. | ||
const data = new CombinedBinaryReader(dataChunks); | ||
// Building the subtitle image. | ||
// Building a canvas element with the subtitle image data. | ||
const canvas = document.createElement('canvas'); | ||
@@ -225,6 +262,8 @@ const context = canvas.getContext('2d')!; | ||
// The pixel data is run-length encoded. The value is the palette entry index. | ||
// The pixel data is run-length encoded. The decoded value is the palette entry index. | ||
RunLengthEncoding.decode(data, (idx, x, y, value) => { | ||
const col = palette?.entries[value]; | ||
if (!col) return; | ||
// Writing the four byte pixel data as RGBA. | ||
buffer[idx * 4] = col.r; | ||
@@ -240,2 +279,6 @@ buffer[idx * 4 + 1] = col.g; | ||
// endregion | ||
// region Dispose | ||
/** | ||
@@ -245,3 +288,3 @@ * Destroys the subtitle canvas and removes event listeners. | ||
public dispose(): void { | ||
this.unregisterEvents(); | ||
this.unregisterVideoEvents(); | ||
@@ -256,2 +299,4 @@ // Do not destroy the canvas if it was provided from an external source. | ||
} | ||
// endregion | ||
} |
import {BinaryReader} from "./binaryReader"; | ||
/** | ||
* A binary reader based on a {@link Uint8Array}. | ||
*/ | ||
export class ArrayBinaryReader implements BinaryReader { | ||
private readonly array: Uint8Array; | ||
private _position: number = 0; | ||
private $position: number = 0; | ||
@@ -13,3 +16,3 @@ public constructor(array: Uint8Array) { | ||
public get position(): number { | ||
return this._position; | ||
return this.$position; | ||
} | ||
@@ -22,10 +25,10 @@ | ||
public readByte(): number { | ||
return this.array[this._position++]; | ||
return this.array[this.$position++]; | ||
} | ||
public readBytes(count: number): Uint8Array { | ||
const data = this.array.slice(this._position, this._position + count); | ||
this._position += count; | ||
const data = this.array.slice(this.$position, this.$position + count); | ||
this.$position += count; | ||
return data; | ||
} | ||
} |
import {BinaryReader} from "./binaryReader"; | ||
import {ArrayBinaryReader} from "./arrayBinaryReader"; | ||
/** | ||
* A binary reader that combines multiple binary readers in one data stream. | ||
*/ | ||
export class CombinedBinaryReader implements BinaryReader { | ||
private readonly subReaders: BinaryReader[]; | ||
private readonly _length: number; | ||
private readonly $length: number; | ||
private _position: number = 0; | ||
private $position: number = 0; | ||
private subReaderIndex: number = 0; | ||
@@ -23,11 +26,11 @@ | ||
} | ||
this._length = length; | ||
this.$length = length; | ||
} | ||
public get position(): number { | ||
return this._position; | ||
return this.$position; | ||
} | ||
public get length(): number { | ||
return this._length; | ||
return this.$length; | ||
} | ||
@@ -39,3 +42,3 @@ | ||
} | ||
this._position++; | ||
this.$position++; | ||
return this.subReaders[this.subReaderIndex].readByte(); | ||
@@ -42,0 +45,0 @@ } |
import {BinaryReader} from "./binaryReader"; | ||
import {ArrayBinaryReader} from "./arrayBinaryReader"; | ||
/** | ||
* Handles run length encoded images. | ||
*/ | ||
export abstract class RunLengthEncoding { | ||
@@ -5,0 +8,0 @@ /** |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59172
44
1165
93