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 CHANGED
@@ -77,29 +77,38 @@ setInterval(() => {
77
77
  }, 1000 / fps);
78
78
  ```
79
79
 
80
- ### Video
80
+ ### Video — live (recommended)
81
81
 
82
- `videoToAsciiFrames` extracts frames from an `HTMLVideoElement` at a given frame rate and returns the full frame sequence.
82
+ `asciifyLiveVideo` streams a video as ASCII art in real time. Pass a URL and a canvas it handles everything else.
83
83
 
84
- ```ts
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
- const video = document.createElement('video');
88
- video.src = '/clip.mp4';
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 opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
90
+ const stop = await asciifyLiveVideo('/clip.mp4', canvas);
94
91
 
95
- // videoToAsciiFrames(video, options, width, height, fps?, maxDurationSeconds?)
96
- const { frames, fps } = await videoToAsciiFrames(video, opts, canvas.width, canvas.height, 12, 10);
92
+ // With options:
93
+ const stop = await asciifyLiveVideo('/clip.mp4', canvas, {
94
+ fontSize: 6,
95
+ artStyle: 'matrix',
96
+ });
97
97
 
98
- let frameIndex = 0;
99
- setInterval(() => {
100
- renderFrameToCanvas(canvas.getContext('2d')!, frames[frameIndex], opts, canvas.width, canvas.height);
101
- frameIndex = (frameIndex + 1) % frames.length;
102
- }, 1000 / fps);
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;