asciify-engine 1.0.32 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +124 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -45
- package/dist/index.d.ts +87 -45
- package/dist/index.js +122 -123
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<p align="left">
|
|
4
4
|
<a href="https://www.npmjs.com/package/asciify-engine"><img src="https://img.shields.io/npm/v/asciify-engine?color=d4ff00&labelColor=0a0a0a&style=flat-square" alt="npm version" /></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/asciify-engine"><img src="https://img.shields.io/npm/dm/asciify-engine?color=d4ff00&labelColor=0a0a0a&style=flat-square" alt="downloads" /></a>
|
|
6
|
-
<a href="https://github.com/ayangabryl/asciify/blob/main/
|
|
6
|
+
<a href="https://github.com/ayangabryl/asciify-engine/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-d4ff00?labelColor=0a0a0a&style=flat-square" alt="MIT license" /></a>
|
|
7
7
|
<a href="https://www.buymeacoffee.com/asciify"><img src="https://img.shields.io/badge/buy_me_a_coffee-support-d4ff00?labelColor=0a0a0a&style=flat-square" alt="Buy Me A Coffee" /></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
package/dist/index.cjs
CHANGED
|
@@ -2285,144 +2285,143 @@ function renderTextBackground(ctx, width, height, text, options = {}, hoverPos)
|
|
|
2285
2285
|
}
|
|
2286
2286
|
|
|
2287
2287
|
// src/core/record.ts
|
|
2288
|
-
function
|
|
2289
|
-
|
|
2290
|
-
fps = 15,
|
|
2291
|
-
maxFrames = 120,
|
|
2292
|
-
format = "gif",
|
|
2293
|
-
quality = 10,
|
|
2294
|
-
scale = 1
|
|
2295
|
-
} = options;
|
|
2296
|
-
const interval = 1e3 / fps;
|
|
2297
|
-
let recording = false;
|
|
2298
|
-
let timerId = -1;
|
|
2299
|
-
const blobs = [];
|
|
2300
|
-
const captureFrame = () => {
|
|
2301
|
-
if (!recording || blobs.length >= maxFrames) return;
|
|
2288
|
+
function captureSnapshot(canvas, { format = "png", quality = 0.92, scale = 1 } = {}) {
|
|
2289
|
+
return new Promise((resolve, reject) => {
|
|
2302
2290
|
let src = canvas;
|
|
2303
2291
|
if (scale !== 1) {
|
|
2304
2292
|
const off = document.createElement("canvas");
|
|
2305
2293
|
off.width = Math.round(canvas.width * scale);
|
|
2306
2294
|
off.height = Math.round(canvas.height * scale);
|
|
2307
2295
|
const offCtx = off.getContext("2d");
|
|
2296
|
+
if (!offCtx) {
|
|
2297
|
+
reject(new Error("captureSnapshot: could not get 2d context"));
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2308
2300
|
offCtx.drawImage(canvas, 0, 0, off.width, off.height);
|
|
2309
2301
|
src = off;
|
|
2310
2302
|
}
|
|
2311
|
-
|
|
2303
|
+
src.toBlob(
|
|
2304
|
+
(blob) => blob ? resolve(blob) : reject(new Error("captureSnapshot: toBlob returned null")),
|
|
2305
|
+
`image/${format}`,
|
|
2306
|
+
quality
|
|
2307
|
+
);
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
async function snapshotAndDownload(canvas, options = {}) {
|
|
2311
|
+
const { filename = "asciify-snapshot", format = "png", ...snapOpts } = options;
|
|
2312
|
+
const blob = await captureSnapshot(canvas, { format, ...snapOpts });
|
|
2313
|
+
const ext = format === "jpeg" ? "jpg" : format;
|
|
2314
|
+
const a = document.createElement("a");
|
|
2315
|
+
a.href = URL.createObjectURL(blob);
|
|
2316
|
+
a.download = `${filename}.${ext}`;
|
|
2317
|
+
a.click();
|
|
2318
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1e4);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// src/core/webcam.ts
|
|
2322
|
+
async function asciifyWebcam(canvas, {
|
|
2323
|
+
fontSize = 10,
|
|
2324
|
+
style = "classic",
|
|
2325
|
+
options = {},
|
|
2326
|
+
liveOptions,
|
|
2327
|
+
mirror = true,
|
|
2328
|
+
constraints = { facingMode: "user" },
|
|
2329
|
+
dpr: dprOverride
|
|
2330
|
+
} = {}) {
|
|
2331
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2332
|
+
throw new Error("asciifyWebcam: getUserMedia is not supported in this browser.");
|
|
2333
|
+
}
|
|
2334
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: constraints });
|
|
2335
|
+
const video = document.createElement("video");
|
|
2336
|
+
video.srcObject = stream;
|
|
2337
|
+
video.muted = true;
|
|
2338
|
+
video.playsInline = true;
|
|
2339
|
+
await new Promise((resolve, reject) => {
|
|
2340
|
+
video.onloadedmetadata = () => resolve();
|
|
2341
|
+
video.onerror = () => reject(new Error("asciifyWebcam: video stream failed to load."));
|
|
2342
|
+
video.play().catch(reject);
|
|
2343
|
+
});
|
|
2344
|
+
const merged = {
|
|
2345
|
+
...DEFAULT_OPTIONS,
|
|
2346
|
+
...ART_STYLE_PRESETS[style],
|
|
2347
|
+
...options,
|
|
2348
|
+
fontSize
|
|
2312
2349
|
};
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const img = new Image();
|
|
2328
|
-
img.onload = () => {
|
|
2329
|
-
gif.addFrame(img, { delay: interval, copy: true });
|
|
2330
|
-
loaded++;
|
|
2331
|
-
if (loaded === total) gif.render();
|
|
2332
|
-
};
|
|
2333
|
-
img.src = dataUrl;
|
|
2334
|
-
});
|
|
2335
|
-
gif.on("finished", (blob) => {
|
|
2336
|
-
const reader = new FileReader();
|
|
2337
|
-
reader.onload = () => resolve(reader.result);
|
|
2338
|
-
reader.readAsDataURL(blob);
|
|
2339
|
-
});
|
|
2340
|
-
gif.on("error", reject);
|
|
2341
|
-
});
|
|
2350
|
+
const ctx = canvas.getContext("2d");
|
|
2351
|
+
if (!ctx) throw new Error("asciifyWebcam: could not get 2d context from canvas.");
|
|
2352
|
+
const deviceRatio = dprOverride ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1) ?? 1;
|
|
2353
|
+
if (deviceRatio !== 1) {
|
|
2354
|
+
ctx.scale(deviceRatio, deviceRatio);
|
|
2355
|
+
}
|
|
2356
|
+
let hoverPos = null;
|
|
2357
|
+
const smoothHover = { x: 0.5, y: 0.5, intensity: 0 };
|
|
2358
|
+
const onMouseMove = (e) => {
|
|
2359
|
+
const rect = canvas.getBoundingClientRect();
|
|
2360
|
+
hoverPos = {
|
|
2361
|
+
x: (e.clientX - rect.left) / rect.width,
|
|
2362
|
+
y: (e.clientY - rect.top) / rect.height
|
|
2363
|
+
};
|
|
2342
2364
|
};
|
|
2343
|
-
const
|
|
2344
|
-
|
|
2345
|
-
throw new Error("[asciify recorder] MediaRecorder not available in this browser.");
|
|
2346
|
-
}
|
|
2347
|
-
const off = document.createElement("canvas");
|
|
2348
|
-
if (frames.length === 0) return "";
|
|
2349
|
-
const probe = new Image();
|
|
2350
|
-
await new Promise((res) => {
|
|
2351
|
-
probe.onload = () => res();
|
|
2352
|
-
probe.src = frames[0];
|
|
2353
|
-
});
|
|
2354
|
-
off.width = probe.naturalWidth;
|
|
2355
|
-
off.height = probe.naturalHeight;
|
|
2356
|
-
const offCtx = off.getContext("2d");
|
|
2357
|
-
const stream = off.captureStream(_fps);
|
|
2358
|
-
const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp9" });
|
|
2359
|
-
const chunks = [];
|
|
2360
|
-
recorder.ondataavailable = (e) => chunks.push(e.data);
|
|
2361
|
-
return new Promise((resolve, reject) => {
|
|
2362
|
-
recorder.onstop = () => {
|
|
2363
|
-
const blob = new Blob(chunks, { type: "video/webm" });
|
|
2364
|
-
const reader = new FileReader();
|
|
2365
|
-
reader.onload = () => resolve(reader.result);
|
|
2366
|
-
reader.readAsDataURL(blob);
|
|
2367
|
-
};
|
|
2368
|
-
recorder.onerror = reject;
|
|
2369
|
-
recorder.start();
|
|
2370
|
-
let idx = 0;
|
|
2371
|
-
const drawNext = () => {
|
|
2372
|
-
if (idx >= frames.length) {
|
|
2373
|
-
recorder.stop();
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
const img = new Image();
|
|
2377
|
-
img.onload = () => {
|
|
2378
|
-
offCtx.drawImage(img, 0, 0);
|
|
2379
|
-
idx++;
|
|
2380
|
-
setTimeout(drawNext, interval);
|
|
2381
|
-
};
|
|
2382
|
-
img.src = frames[idx];
|
|
2383
|
-
};
|
|
2384
|
-
drawNext();
|
|
2385
|
-
});
|
|
2365
|
+
const onMouseLeave = () => {
|
|
2366
|
+
hoverPos = null;
|
|
2386
2367
|
};
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2368
|
+
if (merged.hoverStrength > 0) {
|
|
2369
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
2370
|
+
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2371
|
+
}
|
|
2372
|
+
let cancelled = false;
|
|
2373
|
+
let animId;
|
|
2374
|
+
const startTime = performance.now();
|
|
2375
|
+
const tick = (timestamp) => {
|
|
2376
|
+
if (cancelled) return;
|
|
2377
|
+
if (video.readyState >= video.HAVE_CURRENT_DATA) {
|
|
2378
|
+
const displayW = canvas.width / deviceRatio;
|
|
2379
|
+
const displayH = canvas.height / deviceRatio;
|
|
2380
|
+
const elapsed = (timestamp - startTime) / 1e3;
|
|
2381
|
+
const frameOptions = liveOptions ? { ...merged, ...liveOptions() } : merged;
|
|
2382
|
+
const wantsHover = frameOptions.hoverStrength > 0;
|
|
2383
|
+
if (wantsHover) {
|
|
2384
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
2385
|
+
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2386
|
+
} else {
|
|
2387
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2388
|
+
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2389
|
+
}
|
|
2390
|
+
const { frame } = imageToAsciiFrame(video, frameOptions, displayW, displayH);
|
|
2391
|
+
if (hoverPos) {
|
|
2392
|
+
const dx = hoverPos.x - smoothHover.x;
|
|
2393
|
+
const dy = hoverPos.y - smoothHover.y;
|
|
2394
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
2395
|
+
const speed = Math.min(0.25, 0.06 + dist * 0.8);
|
|
2396
|
+
smoothHover.x += dx * speed;
|
|
2397
|
+
smoothHover.y += dy * speed;
|
|
2398
|
+
smoothHover.intensity += (1 - smoothHover.intensity) * 0.12;
|
|
2399
|
+
} else {
|
|
2400
|
+
smoothHover.intensity *= 0.965;
|
|
2401
|
+
if (smoothHover.intensity < 3e-3) smoothHover.intensity = 0;
|
|
2407
2402
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2403
|
+
const hoverArg = smoothHover.intensity > 3e-3 ? { x: smoothHover.x, y: smoothHover.y, intensity: smoothHover.intensity } : null;
|
|
2404
|
+
if (mirror) {
|
|
2405
|
+
ctx.save();
|
|
2406
|
+
ctx.scale(-1, 1);
|
|
2407
|
+
ctx.translate(-displayW, 0);
|
|
2408
|
+
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2409
|
+
ctx.restore();
|
|
2410
|
+
} else {
|
|
2411
|
+
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2410
2412
|
}
|
|
2411
|
-
return encodeGif(frames);
|
|
2412
2413
|
}
|
|
2414
|
+
animId = requestAnimationFrame(tick);
|
|
2415
|
+
};
|
|
2416
|
+
animId = requestAnimationFrame(tick);
|
|
2417
|
+
return () => {
|
|
2418
|
+
cancelled = true;
|
|
2419
|
+
cancelAnimationFrame(animId);
|
|
2420
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2421
|
+
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2422
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
2423
|
+
video.srcObject = null;
|
|
2413
2424
|
};
|
|
2414
|
-
}
|
|
2415
|
-
async function recordAndDownload(canvas, durationMs, options = {}) {
|
|
2416
|
-
const { filename = "asciify-recording", ...recOpts } = options;
|
|
2417
|
-
const recorder = createRecorder(canvas, recOpts);
|
|
2418
|
-
recorder.start();
|
|
2419
|
-
await new Promise((res) => setTimeout(res, durationMs));
|
|
2420
|
-
const dataUrl = await recorder.stop();
|
|
2421
|
-
const ext = options.format === "webp" ? "webm" : options.format === "png-sequence" ? "json" : "gif";
|
|
2422
|
-
const a = document.createElement("a");
|
|
2423
|
-
a.href = dataUrl;
|
|
2424
|
-
a.download = `${filename}.${ext}`;
|
|
2425
|
-
a.click();
|
|
2426
2425
|
}
|
|
2427
2426
|
|
|
2428
2427
|
exports.ART_STYLE_PRESETS = ART_STYLE_PRESETS;
|
|
@@ -2436,12 +2435,12 @@ exports.asciiTextAnsi = asciiTextAnsi;
|
|
|
2436
2435
|
exports.asciify = asciify;
|
|
2437
2436
|
exports.asciifyGif = asciifyGif;
|
|
2438
2437
|
exports.asciifyVideo = asciifyVideo;
|
|
2438
|
+
exports.asciifyWebcam = asciifyWebcam;
|
|
2439
2439
|
exports.buildTextFrame = buildTextFrame;
|
|
2440
|
-
exports.
|
|
2440
|
+
exports.captureSnapshot = captureSnapshot;
|
|
2441
2441
|
exports.gifToAsciiFrames = gifToAsciiFrames;
|
|
2442
2442
|
exports.imageToAsciiFrame = imageToAsciiFrame;
|
|
2443
2443
|
exports.mountWaveBackground = mountWaveBackground;
|
|
2444
|
-
exports.recordAndDownload = recordAndDownload;
|
|
2445
2444
|
exports.renderAuroraBackground = renderAuroraBackground;
|
|
2446
2445
|
exports.renderCircuitBackground = renderCircuitBackground;
|
|
2447
2446
|
exports.renderDnaBackground = renderDnaBackground;
|
|
@@ -2458,6 +2457,7 @@ exports.renderTerrainBackground = renderTerrainBackground;
|
|
|
2458
2457
|
exports.renderTextBackground = renderTextBackground;
|
|
2459
2458
|
exports.renderVoidBackground = renderVoidBackground;
|
|
2460
2459
|
exports.renderWaveBackground = renderWaveBackground;
|
|
2460
|
+
exports.snapshotAndDownload = snapshotAndDownload;
|
|
2461
2461
|
exports.videoToAsciiFrames = videoToAsciiFrames;
|
|
2462
2462
|
//# sourceMappingURL=index.cjs.map
|
|
2463
2463
|
//# sourceMappingURL=index.cjs.map
|