diff --git a/package.json b/package.json index b825a45..a818683 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "remark-rehype": "^11.1.1", "svelte-meta-tags": "^4.1.0", "tone": "^15.0.4", - "unified": "^11.0.5" + "unified": "^11.0.5", + "zstddec": "^0.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05788ab..c2bcb96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: unified: specifier: ^11.0.5 version: 11.0.5 + zstddec: + specifier: ^0.1.0 + version: 0.1.0 devDependencies: '@sveltejs/adapter-auto': specifier: ^3.3.1 @@ -1699,6 +1702,9 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zstddec@0.1.0: + resolution: {integrity: sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3398,4 +3404,6 @@ snapshots: zimmerframe@1.1.2: {} + zstddec@0.1.0: {} + zwitch@2.0.4: {} diff --git a/src/routes/term/types.ts b/src/routes/term/types.ts index f4723fb..fd59bf3 100644 --- a/src/routes/term/types.ts +++ b/src/routes/term/types.ts @@ -48,7 +48,7 @@ export interface TextVideo2 { fps: number; width: number; height: number; - compression: "gzip" | "none" | ""; + compression: "gzip" | "zstd" | "none" | ""; } frames: { data: Uint8Array; diff --git a/src/routes/term/video2.ts b/src/routes/term/video2.ts index b928e54..b8a14e9 100644 --- a/src/routes/term/video2.ts +++ b/src/routes/term/video2.ts @@ -1,8 +1,6 @@ import type { StdlibType, TextVideo2, Video2WorkerResponse, Video2WorkerMessage } from "./types"; import * as Tone from "tone"; -import axios, { type AxiosResponse } from "axios"; import { unpack, pack } from 'msgpackr'; -import Pako from "pako"; import video2Worker from "./video2.worker?worker"; type PlayOptions = { @@ -48,9 +46,9 @@ export async function play( video = unpack(uint8Array) - if (video.format_version !== 1) { + if (video.format_version !== 1 && video.format_version !== 2) { stdlib.print( - "Unsupported format version, expected 1, got " + video.format_version + "Unsupported format version, expected 1 or 2, got " + video.format_version ); return; } @@ -89,11 +87,13 @@ export async function play( break; case "stats": + console.log(`Received stats from worker: ${data.stats}`); + break; } }; // Send the video data and oneBit option to the worker for decoding. - worker.postMessage({ type: "init", video, oneBit: options.oneBit} as Video2WorkerMessage); + worker.postMessage({ type: "init", video, oneBit: options.oneBit} as Video2WorkerMessage); stdlib.print( "Your video will start in 5 seconds, if the video looks weird then you might need to zoom out." diff --git a/src/routes/term/video2.worker.ts b/src/routes/term/video2.worker.ts index 5e1a881..1991332 100644 --- a/src/routes/term/video2.worker.ts +++ b/src/routes/term/video2.worker.ts @@ -1,9 +1,11 @@ import Pako from 'pako'; +import { ZSTDDecoder } from 'zstddec'; import type { Video2WorkerMessage, Video2WorkerResponse, TextVideo2 } from './types'; let frameBuffer: string[] = []; let video = {} as TextVideo2; let oneBit = false; +const zstdDecoder = new ZSTDDecoder(); // Build the pixel-to-char lookup table (LUT) function createLUT(oneBit: boolean): string[] { @@ -36,9 +38,14 @@ function addFrameToBuffer(index: number, options: { addEvenIfpresent?: boolean } const lut = createLUT(oneBit); const frame = video.frames[index]; let frameData: Uint8Array; - if (video.video_info.compression === 'gzip') { - frameData = Pako.inflate(frame.data); - } else { + switch (video.video_info.compression) { + case "gzip": + frameData = Pako.inflate(frame.data); + break; + case "zstd": + frameData = zstdDecoder.decode(frame.data, video.video_info.width * video.video_info.height); + break; + default: frameData = frame.data; } const textFrame = pixelsToChars(frameData, video.video_info.width, video.video_info.height, lut); @@ -52,22 +59,29 @@ function add5sOfFramesToBuffer(index: number) { } } -self.onmessage = function (e) { +self.onmessage = async function (e) { let data = e.data as Video2WorkerMessage; switch (data.type) { case 'init': video = data.video; oneBit = data.oneBit || false; + if (data.video.video_info.compression === "zstd") { + await zstdDecoder.init(); + } add5sOfFramesToBuffer(0); break; case 'requestFrame': + if (!frameBuffer[data.index]) { + addFrameToBuffer(data.index); + } let frame = frameBuffer[data.index]; - if (frame) { - postMessage({ type: 'frame', index: data.index, text: frame }); - } - add5sOfFramesToBuffer(data.index); + postMessage({ type: 'frame', index: data.index, text: frame }); + add5sOfFramesToBuffer(data.index+1); break; + + case 'stats': + postMessage({ type: 'stats', stats: { buffer: { size: frameBuffer.length, max: video.frames.length }}} as Video2WorkerResponse); } };