asciify-engine 1.0.41 → 1.0.43
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 +62 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +62 -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,67 @@ 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 = {}, onReady, onFrame } = {}) {
|
|
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
|
+
await video.play().catch(() => {
|
|
1107
|
+
});
|
|
1108
|
+
onReady?.(video);
|
|
1109
|
+
} else {
|
|
1110
|
+
video = source;
|
|
1111
|
+
if (video.paused) await video.play().catch(() => {
|
|
1112
|
+
});
|
|
1113
|
+
onReady?.(video);
|
|
1114
|
+
}
|
|
1115
|
+
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1116
|
+
const ctx = canvas.getContext("2d");
|
|
1117
|
+
if (!ctx) throw new Error("asciifyLiveVideo: could not get 2d context from canvas.");
|
|
1118
|
+
let cancelled = false;
|
|
1119
|
+
let animId;
|
|
1120
|
+
const tick = () => {
|
|
1121
|
+
if (cancelled) return;
|
|
1122
|
+
animId = requestAnimationFrame(tick);
|
|
1123
|
+
if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
|
|
1124
|
+
const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
|
|
1125
|
+
if (frame.length > 0) {
|
|
1126
|
+
renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
|
|
1127
|
+
onFrame?.();
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
animId = requestAnimationFrame(tick);
|
|
1131
|
+
return () => {
|
|
1132
|
+
cancelled = true;
|
|
1133
|
+
cancelAnimationFrame(animId);
|
|
1134
|
+
if (ownedVideo) {
|
|
1135
|
+
video.pause();
|
|
1136
|
+
video.src = "";
|
|
1137
|
+
document.body.removeChild(video);
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1080
1141
|
|
|
1081
1142
|
// src/backgrounds/rain.ts
|
|
1082
1143
|
function renderRainBackground(ctx, width, height, time, options = {}) {
|
|
@@ -2468,6 +2529,7 @@ exports.asciiText = asciiText;
|
|
|
2468
2529
|
exports.asciiTextAnsi = asciiTextAnsi;
|
|
2469
2530
|
exports.asciify = asciify;
|
|
2470
2531
|
exports.asciifyGif = asciifyGif;
|
|
2532
|
+
exports.asciifyLiveVideo = asciifyLiveVideo;
|
|
2471
2533
|
exports.asciifyVideo = asciifyVideo;
|
|
2472
2534
|
exports.asciifyWebcam = asciifyWebcam;
|
|
2473
2535
|
exports.buildTextFrame = buildTextFrame;
|