Commit the build output.

Not for upstream: this branch only exists so a consumer can depend on the fork
straight from git while the `ready` promise waits on a release.
This commit is contained in:
2026-08-26 19:23:04 +02:00
parent 382e0f2f03
commit bfd15e68bc
34 changed files with 683 additions and 1 deletions
-1
View File
@@ -1,3 +1,2 @@
/dist/
/node_modules/
/.idea/
+15
View File
@@ -0,0 +1,15 @@
import { PgsRendererMode } from "./pgsRendererMode";
export declare class BrowserSupport {
/**
* Checks if the web worker is supported in the current environment.
*/
static isWorkerSupported(): boolean;
/**
* Checks if the offscreen-canvas and `transferControlToOffscreen` are supported in the current environment.
*/
static isOffscreenCanvasSupported(): boolean;
/**
* Returns the optimal PGS renderer mode for the current platform.
*/
static getRendererModeByPlatform(): PgsRendererMode;
}
+4
View File
@@ -0,0 +1,4 @@
import "core-js/stable/promise";
import "whatwg-fetch";
import { PgsRenderer } from "./pgsRenderer";
export { PgsRenderer };
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+60
View File
@@ -0,0 +1,60 @@
import { DisplaySet } from "./pgs/displaySet";
import { BinaryReader } from "./utils/binaryReader";
import { SubtitleData } from "./subtitleData";
export interface PgsLoadOptions {
/**
* Async pgs streams can return partial updates. When invoked, the `displaySets` and `updateTimestamps` are updated
* to the last available subtitle. There is a minimum threshold of one-second to prevent to many updates.
*/
onProgress?: () => void;
}
/**
* The PGS subtitle data class. This can load and cache sup files from a buffer or url.
* It can also build image data for a given timestamp or timestamp index.
*/
export declare class Pgs {
/**
* The currently loaded display sets.
*/
displaySets: DisplaySet[];
/**
* The PGS timestamps when a display set with the same index is presented.
*/
updateTimestamps: number[];
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
* @param options Optional loading options. Use `onProgress` as callback for partial update while loading.
*/
loadFromUrl(url: string, options?: PgsLoadOptions): Promise<void>;
/**
* Loads the subtitle file from the given buffer.
* @param buffer The PGS data.
* @param options Optional loading options. Use `onProgress` as callback for partial update while loading.
*/
loadFromBuffer(buffer: ArrayBuffer, options?: PgsLoadOptions): Promise<void>;
/**
* Loads the subtitle file from the given buffer.
* @param reader The PGS data reader.
* @param options Optional loading options. Use `onProgress` as callback for partial update while loading.
*/
loadFromReader(reader: BinaryReader, options?: PgsLoadOptions): Promise<void>;
private cachedSubtitleData?;
/**
* Pre-compiles and caches the subtitle data for the given index.
* This will speed up the next call to `buildSubtitleDataAtIndex` with the same index.
* @param index The index of the display set to cache.
*/
cacheSubtitleAtIndex(index: number): void;
/**
* Renders the subtitle at the given timestamp.
* @param time The timestamp in seconds.
*/
getSubtitleAtTimestamp(time: number): SubtitleData | undefined;
/**
* Pre-compiles the required subtitle data (windows and pixel data) for the frame at the given index.
* @param index The index of the display set to render.
*/
getSubtitleAtIndex(index: number): SubtitleData | undefined;
private getPixelDataFromComposition;
}
+23
View File
@@ -0,0 +1,23 @@
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
import { PresentationCompositionSegment } from "./presentationCompositionSegment";
import { PaletteDefinitionSegment } from "./paletteDefinitionSegment";
import { ObjectDefinitionSegment } from "./objectDefinitionSegment";
import { WindowDefinitionSegment } from "./windowDefinitionSegment";
/**
* The PGS display set holds all data for the current subtitle update at a given timestamp.
*/
export declare class DisplaySet {
presentationTimestamp: number;
decodingTimestamp: number;
presentationComposition?: PresentationCompositionSegment;
paletteDefinitions: PaletteDefinitionSegment[];
objectDefinitions: ObjectDefinitionSegment[];
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): Promise<void>;
}
+6
View File
@@ -0,0 +1,6 @@
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
import { Segment } from "./segment";
export declare class EndSegment implements Segment {
get segmentType(): number;
read(reader: BigEndianBinaryReader, length: number): void;
}
+15
View File
@@ -0,0 +1,15 @@
import { Segment } from "./segment";
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
export declare class ObjectDefinitionSegment implements Segment {
id: number;
versionNumber: number;
lastInSequenceFlag: number;
width: number;
height: number;
dataLength: number;
data?: Uint8Array;
get isFirstInSequence(): boolean;
get isLastInSequence(): boolean;
get segmentType(): number;
read(reader: BigEndianBinaryReader, length: number): void;
}
+10
View File
@@ -0,0 +1,10 @@
import { Segment } from "./segment";
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
export declare class PaletteDefinitionSegment implements Segment {
id: number;
versionNumber: number;
rgba: number[];
get segmentType(): number;
read(reader: BigEndianBinaryReader, length: number): void;
private static clamp;
}
+26
View File
@@ -0,0 +1,26 @@
import { Segment } from "./segment";
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
export declare class CompositionObject {
id: number;
windowId: number;
croppedFlag: number;
horizontalPosition: number;
verticalPosition: number;
croppingHorizontalPosition: number;
croppingVerticalPosition: number;
croppingWidth: number;
croppingHeight: number;
get hasCropping(): boolean;
}
export declare class PresentationCompositionSegment implements Segment {
width: number;
height: number;
frameRate: number;
compositionNumber: number;
compositionState: number;
paletteUpdateFlag: number;
paletteId: number;
compositionObjects: CompositionObject[];
get segmentType(): number;
read(reader: BigEndianBinaryReader, length: number): void;
}
+13
View File
@@ -0,0 +1,13 @@
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;
}
+7
View File
@@ -0,0 +1,7 @@
export declare enum SegmentType {
paletteDefinition = 20,
objectDefinition = 21,
presentationComposition = 22,
windowDefinition = 23,
end = 128
}
+14
View File
@@ -0,0 +1,14 @@
import { Segment } from "./segment";
import { BigEndianBinaryReader } from "../utils/bigEndianBinaryReader";
export declare class WindowDefinition {
id: number;
horizontalPosition: number;
verticalPosition: number;
width: number;
height: number;
}
export declare class WindowDefinitionSegment implements Segment {
windows: WindowDefinition[];
get segmentType(): number;
read(reader: BigEndianBinaryReader, length: number): void;
}
+70
View File
@@ -0,0 +1,70 @@
import { PgsRendererOptions } from "./pgsRendererOptions";
/**
* 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);
/**
* Creates the PGS renderer implementation for the given option.
* @param options The PGS renderer options.
*/
private createPgsRenderer;
private implementation;
/**
* Resolves once the last loaded subtitle file - `subUrl` included - can be rendered.
*/
ready: Promise<void>;
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
*/
loadFromUrl(url: string): Promise<void>;
/**
* Loads the subtitle file from the given buffer.
* @param buffer The PGS data.
*/
loadFromBuffer(buffer: ArrayBuffer): Promise<void>;
/**
* Renders the subtitle for the given timestamp.
* @param time The timestamp in seconds.
*/
renderAtTimestamp(time: number): void;
private readonly video?;
private $timeOffset;
/**
* Gets the video-to-subtitle time offset in seconds.
*/
get timeOffset(): number;
/**
* Sets the video-to-subtitle time offset and re-renders the current subtitle if needed.
* @param timeOffset The new time offset in seconds.
*/
set timeOffset(timeOffset: number);
private registerVideoEvents;
private unregisterVideoEvents;
private onTimeUpdate;
private renderAtVideoTimestamp;
private readonly canvas;
private readonly canvasOwner;
private createCanvasElement;
private destroyCanvasElement;
private $aspectRatio;
/**
* Gets the aspect ratio mode of the canvas.
*/
get aspectRatio(): 'contain' | 'cover' | 'fill';
/**
* Sets the aspect ratio mode of the canvas. This should match the `object-fit` property of the video.
* @param aspectMode The aspect mode.
*/
set aspectRatio(aspectMode: 'contain' | 'cover' | 'fill');
/**
* Destroys the subtitle canvas and removes event listeners.
*/
dispose(): void;
}
+9
View File
@@ -0,0 +1,9 @@
export declare class PgsRendererHelper {
/**
* Returns the array index position for the previous timestamp position from the given array.
* Returns -1 if the given time is outside the timestamp range.
* @param time The timestamp to check in seconds.
* @param pgsTimestamps The list of available PGS timestamps.
*/
static getIndexFromTimestamps(time: number, pgsTimestamps: number[]): number;
}
+47
View File
@@ -0,0 +1,47 @@
/**
* The base for handling pgs loading and rendering. There are different ways to render a subtitle file. Modern browser
* support a more efficient asynchrone method using a web-worker. But if this isn't working for the current browser
* a more compatible implementation can be used.
*/
export declare abstract class PgsRendererImpl {
private updateTimestamps;
private previousTimestampIndex;
/**
* Is called when the timestamps were updated.
*/
onTimestampsUpdated?: () => void;
/**
* Sets the update timestamps and invokes an update event.
* @param updateTimestamps The new array of update timestamps.
*/
protected setUpdateTimestamps(updateTimestamps: number[]): void;
/**
* Renders the subtitle for the given timestamp.
* @param time The timestamp in seconds.
*/
renderAtTimestamp(time: number): void;
/**
* Renders the subtitle at the given timestamp index.
* @param index The timestamp index.
*/
renderAtIndex(index: number): void;
/**
* Renders the subtitle at the given timestamp index. Internal render method to overwrite.
* @param index The timestamp index.
*/
protected abstract render(index: number): void;
/**
* Loads the subtitle file from the given url.
* @param url The url to the PGS file.
*/
abstract loadFromUrl(url: string): Promise<void>;
/**
* Loads the subtitle file from the given buffer.
* @param buffer The PGS data.
*/
abstract loadFromBuffer(buffer: ArrayBuffer): Promise<void>;
/**
* Disposes the renderer.
*/
abstract dispose(): void;
}
+29
View File
@@ -0,0 +1,29 @@
import { PgsRendererImpl } from "./pgsRendererImpl";
import { PgsRendererOptions } from "./pgsRendererOptions";
/**
* The implementation without web workers. This loads and renders the subtitle in the main thread.
* This is meant as a compatibility fallback if advanced workers are not supported.
*/
export declare class PgsRendererInMainThread extends PgsRendererImpl {
constructor(options: PgsRendererOptions, canvas: HTMLCanvasElement);
/**
* The PGS loader.
* @private
*/
private readonly pgs;
/**
* The subtitle renderer, running in the main thread.
*/
private readonly renderer;
protected render(index: number): void;
loadFromUrl(url: string): Promise<void>;
loadFromBuffer(buffer: ArrayBuffer): Promise<void>;
/**
* Submits the update timestamps from the pgs loader and invokes events.
*/
private invokeTimestampsUpdate;
/**
* Disposes the renderer.
*/
dispose(): void;
}
+32
View File
@@ -0,0 +1,32 @@
import { PgsRendererImpl } from "./pgsRendererImpl";
import { PgsRendererOptions } from "./pgsRendererOptions";
/**
* The base implementation for a pgs renderer in side a worker.
*/
export declare abstract class PgsRendererInWorker extends PgsRendererImpl {
protected constructor(options: PgsRendererOptions);
loadFromUrl(url: string): Promise<void>;
loadFromBuffer(buffer: ArrayBuffer): Promise<void>;
/**
* The background worker.
*/
protected readonly worker: Worker;
/**
* The loads the worker still has to answer, in the order it answers them.
*/
private readonly pendingLoads;
/**
* Handles messages from the worker.
* @param e The event message.
*/
private readonly $onWorkerMessage;
/**
* Handles messages from the worker.
* @param e The event message.
*/
protected onWorkerMessage(e: MessageEvent): void;
/**
* Disposes the renderer and terminates the worker.
*/
dispose(): void;
}
+10
View File
@@ -0,0 +1,10 @@
import { PgsRendererOptions } from "./pgsRendererOptions";
import { PgsRendererInWorker } from "./pgsRendererInWorker";
/**
* A subtitle renderer running fully in a web-worker. It loads and renders the subtitles in the web-worker.
* This requires browser support for web-workers and offscreen-canvas.
*/
export declare class PgsRendererInWorkerWithOffscreenCanvas extends PgsRendererInWorker {
constructor(options: PgsRendererOptions, canvas: HTMLCanvasElement);
protected render(index: number): void;
}
+15
View File
@@ -0,0 +1,15 @@
import { PgsRendererInWorker } from "./pgsRendererInWorker";
import { PgsRendererOptions } from "./pgsRendererOptions";
/**
* A subtitle renderer running partially in a web-worker. It loads the subtitles in the web-worker, but rendering is
* done on the main thread. This still requires web-workers, but skips the requirement of the offscreen-canvas.
*/
export declare class PgsRendererInWorkerWithoutOffscreenCanvas extends PgsRendererInWorker {
constructor(options: PgsRendererOptions, canvas: HTMLCanvasElement);
/**
* The subtitle renderer, running in the main thread.
*/
private readonly renderer;
protected render(index: number): void;
protected onWorkerMessage(e: MessageEvent): void;
}
+14
View File
@@ -0,0 +1,14 @@
export declare enum PgsRendererMode {
/**
* A worker thread is used to load, build and render the subtitles. This requires OffscreenCanvas support.
*/
worker = "worker",
/**
* A worker thread is used to load and build the subtitles. They are rendered in the main thread.
*/
workerWithoutOffscreenCanvas = "workerWithoutOffscreenCanvas",
/**
* The subtitles are loaded, built and rendered in the main thread.
*/
mainThread = "mainThread"
}
+34
View File
@@ -0,0 +1,34 @@
import { PgsRendererMode } from "./pgsRendererMode";
export interface PgsRendererOptions {
/**
* The video element to sync the subtitle to.
* Optional, if you provide a custom canvas and use `renderAtTimestamp` to manually update the timestamp.
*/
video?: HTMLVideoElement;
/**
* The initial canvas element to draw the subtitles to.
* If not provided, the renderer creates its own canvas next to the video element.
*/
canvas?: HTMLCanvasElement;
/**
* The video-to-subtitle time offset in seconds.
*/
timeOffset?: number;
/**
* The canvas aspect ratio mode. This should match the `object-fit` property of the video.
*/
aspectRatio?: 'contain' | 'cover' | 'fill';
/**
* The initial subtitle file url to load from.
*/
subUrl?: string;
/**
* The url to the worker javascript file.
*/
workerUrl?: string;
/**
* The forced renderer mode.
* If not provided, the renderer checks the current browser to detect the best mode for your platform.
*/
mode?: PgsRendererMode;
}
+28
View File
@@ -0,0 +1,28 @@
import { SubtitleData } from "./subtitleData";
/**
* This handles the low-level PGS loading and rendering. This renderer can operate inside the web worker without being
* linked to a video element.
*/
export declare class Renderer {
private readonly canvas;
private readonly context;
private readonly dirtyArea;
constructor(canvas: OffscreenCanvas | HTMLCanvasElement);
/**
* Renders the given subtitle data to the canvas.
* @param subtitleData The pre-compiled subtitle data to render.
*/
draw(subtitleData?: SubtitleData): void;
/**
* Draws the whole subtitle frame to the given context.
* @param subtitleData The subtitle data to draw.
* @param dirtyArea If given, it will extend the dirty rect to include the affected subtitle area.
*/
private drawSubtitleData;
/**
* Draws this subtitle composition to the given context.
* @param compositionData The subtitle composition data to draw.
* @param dirtyArea If given, it will extend the dirty rect to include the affected subtitle area.
*/
private drawSubtitleCompositionData;
}
+38
View File
@@ -0,0 +1,38 @@
import { WindowDefinition } from "./pgs/windowDefinitionSegment";
import { CompositionObject } from "./pgs/presentationCompositionSegment";
/**
* This class contains the compiled subtitle data for a whole frame.
*/
export declare class SubtitleData {
/**
* The total width of the presentation composition (screen).
*/
readonly width: number;
/**
* The total height of the presentation composition (screen).
*/
readonly height: number;
/**
* The pre-compiled composition elements.
*/
readonly compositionData: SubtitleCompositionData[];
constructor(width: number, height: number, compositionData: SubtitleCompositionData[]);
}
/**
* This class contains the compiled subtitle data for a single composition.
*/
export declare class SubtitleCompositionData {
/**
* The original composition object of the subtile data.
*/
readonly compositionObject: CompositionObject;
/**
* The pgs window to draw on (the on-screen position).
*/
readonly window: WindowDefinition;
/**
* The compiled pixel data of the subtitle.
*/
readonly pixelData: ImageData;
constructor(compositionObject: CompositionObject, window: WindowDefinition, pixelData: ImageData);
}
+14
View File
@@ -0,0 +1,14 @@
import { BinaryReader } from "./binaryReader";
/**
* A binary reader based on a {@link Uint8Array}.
*/
export declare class ArrayBinaryReader implements BinaryReader {
private readonly array;
private $position;
constructor(array: Uint8Array);
get position(): number;
get length(): number;
get eof(): boolean;
readByte(): number;
readBytes(count: number): Uint8Array;
}
+10
View File
@@ -0,0 +1,10 @@
import { BinaryReader } from "./binaryReader";
export interface AsyncBinaryReader extends BinaryReader {
/**
* Ensures that the given number of bytes is available to read synchronously.
* This will wait until the data is ready to read.
* @param count The number of bytes requested.
* @return Returns if the requested number of bytes could be loaded.
*/
requestData(count: number): Promise<boolean>;
}
+16
View File
@@ -0,0 +1,16 @@
import { BinaryReader } from "./binaryReader";
export declare class BigEndianBinaryReader {
/**
* The base binary reader.
*/
readonly baseReader: BinaryReader;
constructor(buffer: BinaryReader | Uint8Array);
get position(): number;
get length(): number;
get eof(): boolean;
readUInt8(): number;
readUInt16(): number;
readUInt24(): number;
readUInt32(): number;
readBytes(count: number): Uint8Array;
}
+23
View File
@@ -0,0 +1,23 @@
export interface BinaryReader {
/**
* Gets the current position in the binary buffer.
*/
get position(): number;
/**
* Gets the length of the binary buffer.
*/
get length(): number;
/**
* Gets if the binary reader has reached the end of the data.
*/
get eof(): boolean;
/**
* Reads a single byte from this buffer.
*/
readByte(): number;
/**
* Reads the given number of bytes from the buffer.
* @param count The number of bytes to read.
*/
readBytes(count: number): Uint8Array;
}
+21
View File
@@ -0,0 +1,21 @@
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 $length;
private $position;
private subReaderIndex;
constructor(subReaders: BinaryReader[] | Uint8Array[]);
/**
* Adding another sub-reader to the collection.
* @param subReader The new sub-reader to add.
*/
push(subReader: BinaryReader | Uint8Array): void;
get position(): number;
get length(): number;
get eof(): boolean;
readByte(): number;
readBytes(count: number): Uint8Array;
}
+45
View File
@@ -0,0 +1,45 @@
/**
* A simple rectangular class.
*/
export declare class Rect {
/**
* Gets if the rect is still empty and doesn't contain any area.
*/
empty: boolean;
/**
* Gets the x coordinate of the rectangular area if not empty.
*/
x: number;
/**
* Gets the y coordinate of the rectangular area if not empty.
*/
y: number;
/**
* Gets the width of the rectangular area if not empty.
*/
width: number;
/**
* Gets the height of the rectangular area if not empty.
*/
height: number;
/**
* Clears the rectangular.
*/
reset(): void;
/**
* Grows this rectangular area to include the given area.
* @param x The x coordinate of the new area.
* @param y The y coordinate of the new area.
* @param width The width of the new area. Negative values are not supported.
* @param height The height of the new area. Negative values are not supported.
*/
set(x: number, y: number, width?: number, height?: number): void;
/**
* Grows this rectangular area to include the given area.
* @param x The x coordinate of the new area to include.
* @param y The y coordinate of the new area to include.
* @param width The width of the new area to include. Negative values are not supported.
* @param height The height of the new area to include. Negative values are not supported.
*/
union(x: number, y: number, width?: number, height?: number): void;
}
+14
View File
@@ -0,0 +1,14 @@
import { BinaryReader } from "./binaryReader";
/**
* Handles run length encoded images.
*/
export declare abstract class RunLengthEncoding {
/**
* Decodes the run length encoded image.
* @param reader The run length encoded binary data reader or buffer.
* @param source The source maps the index value to the raw output pixel data.
* @param target The pixel data is written to the output.
* @return Returns the number of decoded pixels.
*/
static decode(reader: BinaryReader | Uint8Array, source: number[] | Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array, target: number[] | Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array): number;
}
+16
View File
@@ -0,0 +1,16 @@
import { AsyncBinaryReader } from "./asyncBinaryReader";
/**
* A binary reader based on a readable stream. This can read a partially loaded stream - for example a download.
*/
export declare class StreamBinaryReader implements AsyncBinaryReader {
private readonly stream;
private readonly reader;
private $eof;
constructor(stream: ReadableStreamDefaultReader<Uint8Array>);
get position(): number;
get length(): number;
get eof(): boolean;
readByte(): number;
readBytes(count: number): Uint8Array;
requestData(count?: number): Promise<boolean>;
}
+3
View File
@@ -0,0 +1,3 @@
import "core-js/stable/array/find";
import "core-js/stable/promise";
import "whatwg-fetch";