Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements #9

Merged
merged 7 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This library renders the graphical subtitles format PGS _(.sub / .sup)_ in the b

## 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)_.
This project is still in progress. It should be able to play 99% of Blu-ray subtitles _(Yes, I made that number up)_.
But some rare used PGS features - like cropping - aren't implemented yet.

If you know a movie or show that is using the cropping feature, please let me know!
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "libpgs",
"version": "0.4.0",
"version": "0.4.1",
"author": "David Schulte",
"license": "MIT",
"description": "Renderer for graphical subtitles (PGS) in the browser. ",
Expand Down
13 changes: 12 additions & 1 deletion src/pgs/displaySet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {PaletteDefinitionSegment} from "./paletteDefinitionSegment";
import {ObjectDefinitionSegment} from "./objectDefinitionSegment";
import {WindowDefinitionSegment} from "./windowDefinitionSegment";
import {SegmentType} from "./segmentType";
import {AsyncBinaryReader} from "../utils/asyncBinaryReader";

/**
* The PGS display set holds all data for the current subtitle update at a given timestamp.
Expand All @@ -22,7 +23,7 @@ export class DisplaySet {
* @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) {
public async read(reader: BigEndianBinaryReader, includeHeader: boolean) {

// Clear
this.presentationTimestamp = 0;
Expand All @@ -32,6 +33,12 @@ export class DisplaySet {
this.objectDefinitions = [];
this.windowDefinitions = [];

// Handles async readers
let asyncReader: AsyncBinaryReader | undefined = undefined;
if ('requestData' in reader.baseReader) {
asyncReader = reader.baseReader as AsyncBinaryReader;
}

while (true)
{
let presentationTimestamp: number = 0;
Expand All @@ -40,6 +47,7 @@ export class DisplaySet {
// The header is included before every segment. Even for the end segment.
if (includeHeader)
{
await asyncReader?.requestData(10);
const magicNumber = reader.readUInt16();
if (magicNumber != 0x5047) {
throw new Error("Invalid magic number!");
Expand All @@ -49,8 +57,11 @@ export class DisplaySet {
decodingTimestamp = reader.readUInt32();
}

await asyncReader?.requestData(3);
const type = reader.readUInt8();
const size = reader.readUInt16()

await asyncReader?.requestData(size);
switch (type) {
case SegmentType.paletteDefinition:
const pds = new PaletteDefinitionSegment();
Expand Down
56 changes: 26 additions & 30 deletions src/pgs/paletteDefinitionSegment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,10 @@ import {Segment} from "./segment";
import {SegmentType} from "./segmentType";
import {BigEndianBinaryReader} from "../utils/bigEndianBinaryReader";

export class PaletteEntry {
public y: number = 0;
public cr: number = 0;
public cb: number = 0;

public r: number = 0;
public g: number = 0;
public b: number = 0;

public a: number = 0;
}

export class PaletteDefinitionSegment implements Segment {
public id: number = 0;
public versionNumber: number = 0;
public entries: { [key: number]: PaletteEntry } = {};
public rgba: number[] = [];

public get segmentType(): number {
return SegmentType.paletteDefinition;
Expand All @@ -28,30 +16,38 @@ export class PaletteDefinitionSegment implements Segment {
this.versionNumber = reader.readUInt8();

const count = (length - 2) / 5;
this.entries = {};

// Creates a buffer to store the mapping as the 4 byte color data.
const data32 = new Uint32Array(1);
const data8 = new Uint8Array(data32.buffer);

this.rgba = [];
for (let i = 0; i < count; i++) {
const id = reader.readUInt8();
const entry = new PaletteEntry();

// Load the YCrCbA value
entry.y = reader.readUInt8();
entry.cr = reader.readUInt8();
entry.cb = reader.readUInt8();
entry.a = reader.readUInt8();

// Also store the RGBA value
const y = entry.y;
const cb = entry.cb - 128;
const cr = entry.cr - 128;
entry.r = PaletteDefinitionSegment.clamp(Math.round(y + 1.40200 * cr), 0, 255);
entry.g = PaletteDefinitionSegment.clamp(Math.round(y - 0.34414 * cb - 0.71414 * cr), 0, 255);
entry.b = PaletteDefinitionSegment.clamp(Math.round(y + 1.77200 * cb), 0, 255);

this.entries[id] = entry;
const y = reader.readUInt8();
const cr = reader.readUInt8() - 128;
const cb = reader.readUInt8() - 128;
const a = reader.readUInt8();

// Convert to rgba
const r = PaletteDefinitionSegment.clamp(Math.round(y + 1.40200 * cr), 0, 255);
const g = PaletteDefinitionSegment.clamp(Math.round(y - 0.34414 * cb - 0.71414 * cr), 0, 255);
const b = PaletteDefinitionSegment.clamp(Math.round(y + 1.77200 * cb), 0, 255);

// Convert to 32bit number for faster copy in the image decode.
// We cannot use the bit-shifting here. The buffers will keep the systems endianness. The same endianness
// must be used to write the rgba values to the pixel buffer to even out.
data8[0] = r;
data8[1] = g;
data8[2] = b;
data8[3] = a;
this.rgba[id] = data32[0];
}
}

private static clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(value, max));
return value < min ? min : value > max ? max : value;
}
}
14 changes: 3 additions & 11 deletions src/pgsRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {PgsRendererOptions} from "./pgsRendererOptions";
import {PgsRendererHelper} from "./pgsRendererHelper";

/**
* Renders PGS subtitle on-top of a video element using a canvas element. This also handles timestamp updates if a
Expand Down Expand Up @@ -132,17 +133,8 @@ export class PgsRenderer {
* @param time The timestamp in seconds.
*/
public renderAtTimestamp(time: number): void {
time = time * 1000 * 90; // Convert to PGS time
const index = PgsRendererHelper.getIndexFromTimestamps(time, this.updateTimestamps);

// Find the last subtitle index for the given time stamp
let index = -1;
for (const updateTimestamp of this.updateTimestamps) {

if (updateTimestamp > time) {
break;
}
index++;
}
// Only tell the worker, if the subtitle index was changed!
if (this.previousTimestampIndex === index) return;
this.previousTimestampIndex = index;
Expand All @@ -163,7 +155,7 @@ export class PgsRenderer {
private onWorkerMessage = (e: MessageEvent) => {
switch (e.data.op) {
// Is called once a subtitle file was loaded.
case 'loaded':
case 'updateTimestamps':
// 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;
Expand Down
27 changes: 27 additions & 0 deletions src/pgsRendererHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export 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.
*/
public static getIndexFromTimestamps(time: number, pgsTimestamps: number[]): number {
const pgsTime = time * 1000 * 90; // Convert to PGS time

// All position before and after the available timestamps are invalid (-1).
let index = -1;
if (pgsTimestamps.length > 0 && pgsTime < pgsTimestamps[pgsTimestamps.length - 1]) {

// Find the last subtitle index for the given time stamp
for (const pgsTimestamp of pgsTimestamps) {

if (pgsTimestamp > pgsTime) {
break;
}
index++;
}
}

return index;
}
}
Loading