asciify-engine 1.0.41 → 1.0.42
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 +31 -17
- package/dist/index.cjs +57 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +57 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,29 +77,38 @@ setInterval(() => {
|
|
|
77
77
|
}, 1000 / fps);
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
### Video
|
|
80
|
+
### Video — live (recommended)
|
|
81
81
|
|
|
82
|
-
`
|
|
82
|
+
`asciifyLiveVideo` streams a video as ASCII art in real time. Pass a URL and a canvas — it handles everything else.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
84
|
+
> ⚠️ Never set the `<video>` element to `display: none`. Browsers skip GPU frame decoding for hidden elements — you get a blank canvas. `asciifyLiveVideo` handles this automatically.
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
video.muted = true;
|
|
90
|
-
await new Promise(r => (video.onloadeddata = r));
|
|
86
|
+
```ts
|
|
87
|
+
import { asciifyLiveVideo } from 'asciify-engine';
|
|
91
88
|
|
|
92
89
|
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
93
|
-
const
|
|
90
|
+
const stop = await asciifyLiveVideo('/clip.mp4', canvas);
|
|
94
91
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
92
|
+
// With options:
|
|
93
|
+
const stop = await asciifyLiveVideo('/clip.mp4', canvas, {
|
|
94
|
+
fontSize: 6,
|
|
95
|
+
artStyle: 'matrix',
|
|
96
|
+
});
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
// Clean up:
|
|
99
|
+
stop();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Video — pre-extracted frames
|
|
103
|
+
|
|
104
|
+
`asciifyVideo` seeks through the clip frame by frame and returns a frame sequence. Good for short clips where you want frame-perfect control, but requires up-front processing time.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { asciifyVideo } from 'asciify-engine';
|
|
108
|
+
|
|
109
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
110
|
+
const stop = await asciifyVideo('/clip.mp4', canvas, { fontSize: 8 });
|
|
111
|
+
stop();
|
|
103
112
|
```
|
|
104
113
|
|
|
105
114
|
---
|
|
@@ -245,11 +254,16 @@ const animatedHtml = generateAnimatedEmbedCode(frames, options, fps);
|
|
|
245
254
|
|
|
246
255
|
| Function | Signature | Returns |
|
|
247
256
|
|---|---|---|
|
|
257
|
+
| `asciify` | `(source, canvas, options?)` | `Promise<void>` |
|
|
258
|
+
| `asciifyLiveVideo` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
259
|
+
| `asciifyVideo` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
260
|
+
| `asciifyGif` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
261
|
+
| `asciifyWebcam` | `(canvas, options?)` | `Promise<() => void>` |
|
|
262
|
+
| `asciiBackground` | `(selector, options)` | `() => void` |
|
|
248
263
|
| `imageToAsciiFrame` | `(source, options, w?, h?)` | `{ frame, cols, rows }` |
|
|
249
264
|
| `renderFrameToCanvas` | `(ctx, frame, options, w, h, time?, hoverPos?)` | `void` |
|
|
250
265
|
| `gifToAsciiFrames` | `(buffer, options, w, h, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
251
266
|
| `videoToAsciiFrames` | `(video, options, w, h, fps?, maxSec?, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
252
|
-
| `asciiBackground` | `(selector, options)` | `() => void` (cleanup) |
|
|
253
267
|
| `generateEmbedCode` | `(frame, options)` | `string` |
|
|
254
268
|
| `generateAnimatedEmbedCode` | `(frames, options, fps)` | `string` |
|
|
255
269
|
|
package/dist/index.cjs
CHANGED
|
@@ -1077,6 +1077,62 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1077
1077
|
cancelAnimationFrame(animId);
|
|
1078
1078
|
};
|
|
1079
1079
|
}
|
|
1080
|
+
async function asciifyLiveVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
|
|
1081
|
+
let video;
|
|
1082
|
+
let ownedVideo = false;
|
|
1083
|
+
if (typeof source === "string") {
|
|
1084
|
+
video = document.createElement("video");
|
|
1085
|
+
video.src = source;
|
|
1086
|
+
video.muted = true;
|
|
1087
|
+
video.loop = true;
|
|
1088
|
+
video.playsInline = true;
|
|
1089
|
+
video.setAttribute("playsinline", "");
|
|
1090
|
+
Object.assign(video.style, {
|
|
1091
|
+
position: "fixed",
|
|
1092
|
+
top: "0",
|
|
1093
|
+
left: "0",
|
|
1094
|
+
width: "1px",
|
|
1095
|
+
height: "1px",
|
|
1096
|
+
opacity: "0",
|
|
1097
|
+
pointerEvents: "none",
|
|
1098
|
+
zIndex: "-1"
|
|
1099
|
+
});
|
|
1100
|
+
document.body.appendChild(video);
|
|
1101
|
+
ownedVideo = true;
|
|
1102
|
+
await new Promise((resolve, reject) => {
|
|
1103
|
+
video.onloadedmetadata = () => resolve();
|
|
1104
|
+
video.onerror = () => reject(new Error(`asciifyLiveVideo: failed to load "${source}"`));
|
|
1105
|
+
});
|
|
1106
|
+
video.play().catch(() => {
|
|
1107
|
+
});
|
|
1108
|
+
} else {
|
|
1109
|
+
video = source;
|
|
1110
|
+
if (video.paused) video.play().catch(() => {
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1114
|
+
const ctx = canvas.getContext("2d");
|
|
1115
|
+
if (!ctx) throw new Error("asciifyLiveVideo: could not get 2d context from canvas.");
|
|
1116
|
+
let cancelled = false;
|
|
1117
|
+
let animId;
|
|
1118
|
+
const tick = () => {
|
|
1119
|
+
if (cancelled) return;
|
|
1120
|
+
animId = requestAnimationFrame(tick);
|
|
1121
|
+
if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
|
|
1122
|
+
const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
|
|
1123
|
+
if (frame.length > 0) renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
|
|
1124
|
+
};
|
|
1125
|
+
animId = requestAnimationFrame(tick);
|
|
1126
|
+
return () => {
|
|
1127
|
+
cancelled = true;
|
|
1128
|
+
cancelAnimationFrame(animId);
|
|
1129
|
+
if (ownedVideo) {
|
|
1130
|
+
video.pause();
|
|
1131
|
+
video.src = "";
|
|
1132
|
+
document.body.removeChild(video);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1080
1136
|
|
|
1081
1137
|
// src/backgrounds/rain.ts
|
|
1082
1138
|
function renderRainBackground(ctx, width, height, time, options = {}) {
|
|
@@ -2468,6 +2524,7 @@ exports.asciiText = asciiText;
|
|
|
2468
2524
|
exports.asciiTextAnsi = asciiTextAnsi;
|
|
2469
2525
|
exports.asciify = asciify;
|
|
2470
2526
|
exports.asciifyGif = asciifyGif;
|
|
2527
|
+
exports.asciifyLiveVideo = asciifyLiveVideo;
|
|
2471
2528
|
exports.asciifyVideo = asciifyVideo;
|
|
2472
2529
|
exports.asciifyWebcam = asciifyWebcam;
|
|
2473
2530
|
exports.buildTextFrame = buildTextFrame;
|