Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

libpgs

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

libpgs - npm Package Compare versions

Comparing version 0.2.2 to 0.3.0

.github/workflows/test.yml

2

dist/libpgs.js

@@ -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),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})()));
!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={97:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PgsRenderer=void 0,t.PgsRenderer=class{constructor(e){var t,s;if(this.$timeOffset=0,this.onTimeUpdate=()=>{this.renderAtVideoTimestamp()},this.updateTimestamps=[],this.previousTimestampIndex=0,this.onWorkerMessage=e=>{"loaded"===e.data.op&&(this.updateTimestamps=e.data.updateTimestamps,this.renderAtVideoTimestamp())},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.transferControlToOffscreen(),r=null!==(t=e.workerUrl)&&void 0!==t?t:"libpgs.worker.js";this.worker=new Worker(r),this.worker.onmessage=this.onWorkerMessage,this.worker.postMessage({op:"init",canvas:i},[i]),this.$timeOffset=null!==(s=e.timeOffset)&&void 0!==s?s:0,e.subUrl&&this.loadFromUrl(e.subUrl),this.registerVideoEvents()}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)}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()}renderAtTimestamp(e){e=1e3*e*90;let t=-1;for(const s of this.updateTimestamps){if(s>e)break;t++}this.previousTimestampIndex!=t&&(this.previousTimestampIndex=t,t<0||this.worker.postMessage({op:"render",index:t}))}loadFromUrl(e){this.worker.postMessage({op:"loadFromUrl",url:e})}loadFromBuffer(e){this.worker.postMessage({op:"loadFromBuffer",buffer:e})}dispose(){this.worker.terminate(),this.unregisterVideoEvents(),this.canvasOwner&&this.destroyCanvasElement()}}}},t={};function s(i){var r=t[i];if(void 0!==r)return r.exports;var o=t[i]={exports:{}};return e[i](o,o.exports,s),o.exports}var i={};return(()=>{var e=i;Object.defineProperty(e,"__esModule",{value:!0}),e.PgsRenderer=void 0;const t=s(97);Object.defineProperty(e,"PgsRenderer",{enumerable:!0,get:function(){return t.PgsRenderer}})})(),i})()));

@@ -5,2 +5,4 @@ import { PgsRendererOptions } from "./pgsRendererOptions";

* video element is provided.
*
* The actual rendering is done by {@link PgsRendererInternal} inside a web-worker so optimize performance.
*/

@@ -13,7 +15,2 @@ export declare class PgsRenderer {

constructor(options: PgsRendererOptions);
private readonly canvas;
private readonly canvasOwner;
private readonly context;
private createCanvasElement;
private destroyCanvasElement;
private readonly video?;

@@ -34,9 +31,20 @@ private $timeOffset;

private renderAtVideoTimestamp;
private displaySets;
private displaySetIndex;
private readonly canvas;
private readonly canvasOwner;
private createCanvasElement;
private destroyCanvasElement;
private updateTimestamps;
private previousTimestampIndex;
/**
* Renders the subtitle for the given timestamp.
* @param time The timestamp in seconds.
*/
renderAtTimestamp(time: number): void;
private readonly worker;
private onWorkerMessage;
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
*/
loadFromUrlAsync(url: string): Promise<void>;
loadFromUrl(url: string): void;
/**

@@ -48,10 +56,2 @@ * Loads the subtitle file from the given buffer.

/**
* Renders the subtitle for the given timestamp.
* @param time The timestamp in seconds.
*/
renderAtTimestamp(time: number): void;
private renderDisplaySet;
private renderDisplaySetComposition;
private getPixelDataFromDisplaySetComposition;
/**
* Destroys the subtitle canvas and removes event listeners.

@@ -58,0 +58,0 @@ */

@@ -20,2 +20,6 @@ export interface PgsRendererOptions {

subUrl?: string;
/**
* The url to the worker javascript file.
*/
workerUrl?: string;
}
{
"name": "libpgs",
"version": "0.2.2",
"version": "0.3.0",
"author": "David Schulte",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -10,9 +10,10 @@ # libpgs-js

- [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.
If you know a movie or show that is using the cropping feature, please let me know!
## Requirements
This library requires the following web features:
- [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)
- [Web Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
## Usage

@@ -32,4 +33,6 @@

const pgsRenderer = new libpgs.PgsRenderer({
video: videoElement,
subUrl: './subtitle.sup'
// Make sure your bundler keeps this file accessible from the web!
workerUrl: './node_modules/libpgs/dist/libpgs.worker.js',
video: videoElement,
subUrl: './subtitle.sup'
});

@@ -68,5 +71,7 @@ ```

const pgsRenderer = new libpgs.PgsRenderer({
video: videoElement,
canvas: canvasElement,
subUrl: './subtitle.sup'
// Make sure your bundler keeps this file accessible from the web!
workerUrl: './node_modules/libpgs/dist/libpgs.worker.js',
video: videoElement,
canvas: canvasElement,
subUrl: './subtitle.sup'
});

@@ -73,0 +78,0 @@ ```

@@ -1,7 +0,2 @@

import {DisplaySet} from "./pgs/displaySet";
import {BigEndianBinaryReader} from "./utils/bigEndianBinaryReader";
import {RunLengthEncoding} from "./utils/runLengthEncoding";
import {CompositionObject} from "./pgs/presentationCompositionSegment";
import {PgsRendererOptions} from "./pgsRendererOptions";
import {CombinedBinaryReader} from "./utils/combinedBinaryReader";

@@ -11,5 +6,6 @@ /**

* video element is provided.
*
* The actual rendering is done by {@link PgsRendererInternal} inside a web-worker so optimize performance.
*/
export class PgsRenderer {
/**

@@ -24,2 +20,3 @@ * Creates and starts a PGS subtitle render with the given option.

// Init canvas
if (options.canvas) {

@@ -38,12 +35,17 @@ // Use a canvas provided by the user

const context = this.canvas.getContext('2d');
if (!context) {
throw new Error('Can not create 2d canvas context!');
}
this.context = context;
// Init worker
const offscreenCanvas = this.canvas.transferControlToOffscreen();
const workerUrl = options.workerUrl ?? 'libpgs.worker.js';
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onWorkerMessage;
this.worker.postMessage({
op: 'init',
canvas: offscreenCanvas,
}, [offscreenCanvas])
// Load initial settings
this.$timeOffset = options.timeOffset ?? 0;
if (options.subUrl) {
this.loadFromUrlAsync(options.subUrl).then();
this.loadFromUrl(options.subUrl);
}

@@ -54,28 +56,2 @@

// region Canvas
private readonly canvas: HTMLCanvasElement;
private readonly canvasOwner: boolean;
private readonly context: CanvasRenderingContext2D;
private createCanvasElement(): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.right = '0';
canvas.style.bottom = '0';
canvas.style.pointerEvents = 'none';
canvas.style.objectFit = 'contain';
canvas.style.width = '100%';
canvas.style.height = '100%';
return canvas;
}
private destroyCanvasElement() {
this.canvas.remove();
}
// endregion
// region Video

@@ -128,31 +104,23 @@

// region Subtitle
// region Canvas
private displaySets: DisplaySet[] = [];
private displaySetIndex: number = -1;
private readonly canvas: HTMLCanvasElement;
private readonly canvasOwner: boolean;
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
*/
public async loadFromUrlAsync(url: string): Promise<void> {
const result = await fetch(url);
const buffer = await result.arrayBuffer();
this.loadFromBuffer(buffer);
private createCanvasElement(): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.right = '0';
canvas.style.bottom = '0';
canvas.style.pointerEvents = 'none';
canvas.style.objectFit = 'contain';
canvas.style.width = '100%';
canvas.style.height = '100%';
return canvas;
}
/**
* Loads the subtitle file from the given buffer.
* @param buffer The PGS data.
*/
public loadFromBuffer(buffer: ArrayBuffer): void {
this.displaySets = [];
const reader = new BigEndianBinaryReader(new Uint8Array(buffer));
while (reader.position < reader.length) {
const displaySet = new DisplaySet();
displaySet.read(reader, true);
this.displaySets.push(displaySet);
}
this.renderAtVideoTimestamp();
private destroyCanvasElement() {
this.canvas.remove();
}

@@ -164,2 +132,5 @@

private updateTimestamps: number[] = [];
private previousTimestampIndex: number = 0;
/**

@@ -172,7 +143,7 @@ * Renders the subtitle for the given timestamp.

// Find the last display set index for the given time stamp
// Find the last subtitle index for the given time stamp
let index = -1;
for (const displaySet of this.displaySets) {
for (const updateTimestamp of this.updateTimestamps) {
if (displaySet.presentationTimestamp > time) {
if (updateTimestamp > time) {
break;

@@ -182,89 +153,54 @@ }

}
// No need to update
if (this.displaySetIndex == index) return;
this.displaySetIndex = index;
// Only tell the worker, if the subtitle index was changed!
if (this.previousTimestampIndex == index) return;
this.previousTimestampIndex = index;
// Tell the worker to render
if (index < 0) return;
const displaySet= this.displaySets[index];
this.renderDisplaySet(displaySet);
this.worker.postMessage({
op: 'render',
index: index
});
}
private renderDisplaySet(displaySet: DisplaySet) {
if (!displaySet.presentationComposition) return;
// endregion
// Setting the width and height will also clear the canvas.
this.canvas.width = displaySet.presentationComposition.width;
this.canvas.height = displaySet.presentationComposition.height;
// region Worker
for (const composition of displaySet.presentationComposition.compositionObjects) {
this.renderDisplaySetComposition(displaySet, composition);
}
}
private readonly worker: Worker;
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);
if (!window) return;
private onWorkerMessage = (e: MessageEvent) => {
switch (e.data.op) {
// Is called once a subtitle file was loaded.
case 'loaded':
// Stores the update timestamps, so we don't need to push the timestamp to the worker on every tick.
// Instead, we push the timestamp index if it was changed.
this.updateTimestamps = e.data.updateTimestamps;
const pixelData = this.getPixelDataFromDisplaySetComposition(displaySet, composition);
if (pixelData) {
this.context.drawImage(pixelData, window.horizontalPosition, window.verticalPosition);
// Skip to the current timestamp
this.renderAtVideoTimestamp();
break;
}
}
private getPixelDataFromDisplaySetComposition(displaySet: DisplaySet, composition: CompositionObject):
HTMLCanvasElement | undefined {
if (!displaySet.presentationComposition) return undefined;
let palette = displaySet.paletteDefinitions
.find(p => p.id === displaySet.presentationComposition?.paletteId);
if (!palette) return undefined;
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
*/
public loadFromUrl(url: string): void {
this.worker.postMessage({
op: 'loadFromUrl',
url: url,
})
}
// Multiple object definition can define a single subtitle image.
// However, only the first element in sequence hold the image size.
let width: number = 0;
let height: number = 0;
const dataChunks: Uint8Array[] = [];
for (const ods of displaySet.objectDefinitions) {
if (ods.id != composition.id) continue;
if (ods.isFirstInSequence) {
width = ods.width;
height = ods.height;
}
if (ods.data) {
dataChunks.push(ods.data);
}
}
if (dataChunks.length == 0) {
return undefined;
}
// Using a combined reader instead of stitching the data together.
// This hopefully avoids a larger memory allocation.
const data = new CombinedBinaryReader(dataChunks);
// Building a canvas element with the subtitle image data.
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
canvas.width = width;
canvas.height = height;
const imageData = context.createImageData(width, height);
const buffer = imageData.data;
// 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;
buffer[idx * 4 + 1] = col.g;
buffer[idx * 4 + 2] = col.b;
buffer[idx * 4 + 3] = col.a;
});
context.putImageData(imageData, 0, 0);
return canvas;
/**
* Loads the subtitle file from the given buffer.
* @param buffer The PGS data.
*/
public loadFromBuffer(buffer: ArrayBuffer): void {
this.worker.postMessage({
op: 'loadFromBuffer',
buffer: buffer,
})
}

@@ -280,2 +216,3 @@

public dispose(): void {
this.worker.terminate();
this.unregisterVideoEvents();

@@ -287,5 +224,2 @@

}
// Clear memory
this.displaySets = [];
}

@@ -292,0 +226,0 @@

@@ -23,2 +23,7 @@ export interface PgsRendererOptions {

subUrl?: string;
/**
* The url to the worker javascript file.
*/
workerUrl?: string;
}

@@ -5,3 +5,2 @@ const path = require("path");

mode: 'production',
entry: './src/libpgs.ts',
module: {

@@ -19,8 +18,15 @@ rules: [

},
entry: {
'libpgs': {
import: './src/libpgs.ts',
library: { name: 'libpgs', type: 'umd' },
},
'libpgs.worker': {
import: './src/worker.ts'
}
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'libpgs.js',
library: 'libpgs',
libraryTarget: 'umd'
filename: '[name].js'
}
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc