asciify-engine 1.0.48 → 1.0.50
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/dist/index.cjs +37 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +37 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -224,7 +224,7 @@ declare function imageToAsciiFrame(source: HTMLImageElement | HTMLVideoElement |
|
|
|
224
224
|
/**
|
|
225
225
|
* Extract frames from a video element for ASCII animation.
|
|
226
226
|
*/
|
|
227
|
-
declare function videoToAsciiFrames(video: HTMLVideoElement, options: AsciiOptions, targetWidth: number, targetHeight: number, targetFps?: number, maxDuration?: number, onProgress?: (progress: number) => void): Promise<{
|
|
227
|
+
declare function videoToAsciiFrames(video: HTMLVideoElement, options: AsciiOptions, targetWidth: number, targetHeight: number, targetFps?: number, maxDuration?: number, onProgress?: (progress: number) => void, startTime?: number): Promise<{
|
|
228
228
|
frames: AsciiFrame[];
|
|
229
229
|
cols: number;
|
|
230
230
|
rows: number;
|
|
@@ -288,6 +288,20 @@ interface AsciifyVideoOptions extends AsciifySimpleOptions {
|
|
|
288
288
|
* ⚠️ Memory-intensive. Capped at 10 s / 300 frames.
|
|
289
289
|
*/
|
|
290
290
|
preExtract?: boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Trim the video to a specific time range (in seconds).
|
|
293
|
+
* - `start` — seek to this time before playback begins. Default: `0`
|
|
294
|
+
* - `end` — loop back to `start` when this time is reached.
|
|
295
|
+
* In `preExtract` mode, only frames up to `end` are extracted.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* // Play only seconds 2–8, looping:
|
|
299
|
+
* asciifyVideo('/clip.mp4', canvas, { trim: { start: 2, end: 8 } });
|
|
300
|
+
*/
|
|
301
|
+
trim?: {
|
|
302
|
+
start?: number;
|
|
303
|
+
end?: number;
|
|
304
|
+
};
|
|
291
305
|
/**
|
|
292
306
|
* Called once when the video metadata is loaded and playback has started.
|
|
293
307
|
* Receives the backing video element.
|
|
@@ -341,7 +355,7 @@ declare function asciifyGif(source: string | ArrayBuffer, canvas: HTMLCanvasElem
|
|
|
341
355
|
* // Pre-extract frames (old behavior):
|
|
342
356
|
* const stop = await asciifyVideo('/clip.mp4', canvas, { preExtract: true });
|
|
343
357
|
*/
|
|
344
|
-
declare function asciifyVideo(source: HTMLVideoElement | string, canvas: HTMLCanvasElement, { fontSize, artStyle, options, fitTo, preExtract, onReady, onFrame }?: AsciifyVideoOptions): Promise<() => void>;
|
|
358
|
+
declare function asciifyVideo(source: HTMLVideoElement | string, canvas: HTMLCanvasElement, { fontSize, artStyle, options, fitTo, preExtract, trim, onReady, onFrame }?: AsciifyVideoOptions): Promise<() => void>;
|
|
345
359
|
/**
|
|
346
360
|
* @deprecated Use {@link asciifyVideo} instead — it now defaults to live streaming
|
|
347
361
|
* and accepts the same options including `fitTo` and `preExtract`.
|
package/dist/index.d.ts
CHANGED
|
@@ -224,7 +224,7 @@ declare function imageToAsciiFrame(source: HTMLImageElement | HTMLVideoElement |
|
|
|
224
224
|
/**
|
|
225
225
|
* Extract frames from a video element for ASCII animation.
|
|
226
226
|
*/
|
|
227
|
-
declare function videoToAsciiFrames(video: HTMLVideoElement, options: AsciiOptions, targetWidth: number, targetHeight: number, targetFps?: number, maxDuration?: number, onProgress?: (progress: number) => void): Promise<{
|
|
227
|
+
declare function videoToAsciiFrames(video: HTMLVideoElement, options: AsciiOptions, targetWidth: number, targetHeight: number, targetFps?: number, maxDuration?: number, onProgress?: (progress: number) => void, startTime?: number): Promise<{
|
|
228
228
|
frames: AsciiFrame[];
|
|
229
229
|
cols: number;
|
|
230
230
|
rows: number;
|
|
@@ -288,6 +288,20 @@ interface AsciifyVideoOptions extends AsciifySimpleOptions {
|
|
|
288
288
|
* ⚠️ Memory-intensive. Capped at 10 s / 300 frames.
|
|
289
289
|
*/
|
|
290
290
|
preExtract?: boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Trim the video to a specific time range (in seconds).
|
|
293
|
+
* - `start` — seek to this time before playback begins. Default: `0`
|
|
294
|
+
* - `end` — loop back to `start` when this time is reached.
|
|
295
|
+
* In `preExtract` mode, only frames up to `end` are extracted.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* // Play only seconds 2–8, looping:
|
|
299
|
+
* asciifyVideo('/clip.mp4', canvas, { trim: { start: 2, end: 8 } });
|
|
300
|
+
*/
|
|
301
|
+
trim?: {
|
|
302
|
+
start?: number;
|
|
303
|
+
end?: number;
|
|
304
|
+
};
|
|
291
305
|
/**
|
|
292
306
|
* Called once when the video metadata is loaded and playback has started.
|
|
293
307
|
* Receives the backing video element.
|
|
@@ -341,7 +355,7 @@ declare function asciifyGif(source: string | ArrayBuffer, canvas: HTMLCanvasElem
|
|
|
341
355
|
* // Pre-extract frames (old behavior):
|
|
342
356
|
* const stop = await asciifyVideo('/clip.mp4', canvas, { preExtract: true });
|
|
343
357
|
*/
|
|
344
|
-
declare function asciifyVideo(source: HTMLVideoElement | string, canvas: HTMLCanvasElement, { fontSize, artStyle, options, fitTo, preExtract, onReady, onFrame }?: AsciifyVideoOptions): Promise<() => void>;
|
|
358
|
+
declare function asciifyVideo(source: HTMLVideoElement | string, canvas: HTMLCanvasElement, { fontSize, artStyle, options, fitTo, preExtract, trim, onReady, onFrame }?: AsciifyVideoOptions): Promise<() => void>;
|
|
345
359
|
/**
|
|
346
360
|
* @deprecated Use {@link asciifyVideo} instead — it now defaults to live streaming
|
|
347
361
|
* and accepts the same options including `fitTo` and `preExtract`.
|
package/dist/index.js
CHANGED
|
@@ -692,15 +692,15 @@ function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
|
692
692
|
}
|
|
693
693
|
return { frame, cols, rows };
|
|
694
694
|
}
|
|
695
|
-
async function videoToAsciiFrames(video, options, targetWidth, targetHeight, targetFps = 12, maxDuration = 10, onProgress) {
|
|
696
|
-
const duration = Math.min(video.duration, maxDuration);
|
|
695
|
+
async function videoToAsciiFrames(video, options, targetWidth, targetHeight, targetFps = 12, maxDuration = 10, onProgress, startTime = 0) {
|
|
696
|
+
const duration = Math.min(video.duration - startTime, maxDuration);
|
|
697
697
|
const totalFrames = Math.ceil(duration * targetFps);
|
|
698
698
|
const frames = [];
|
|
699
699
|
let cols = 0;
|
|
700
700
|
let rows = 0;
|
|
701
701
|
for (let i = 0; i < totalFrames; i++) {
|
|
702
|
-
const time = i / targetFps;
|
|
703
|
-
if (time > duration) break;
|
|
702
|
+
const time = startTime + i / targetFps;
|
|
703
|
+
if (time > startTime + duration) break;
|
|
704
704
|
video.currentTime = time;
|
|
705
705
|
await new Promise((resolve) => {
|
|
706
706
|
const handler = () => {
|
|
@@ -1087,7 +1087,9 @@ async function asciifyGif(source, canvas, { fontSize = 10, artStyle = "classic",
|
|
|
1087
1087
|
cancelAnimationFrame(animId);
|
|
1088
1088
|
};
|
|
1089
1089
|
}
|
|
1090
|
-
async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {}, fitTo, preExtract = false, onReady, onFrame } = {}) {
|
|
1090
|
+
async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {}, fitTo, preExtract = false, trim, onReady, onFrame } = {}) {
|
|
1091
|
+
const trimStart = trim?.start ?? 0;
|
|
1092
|
+
const trimEnd = trim?.end;
|
|
1091
1093
|
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1092
1094
|
const ctx = canvas.getContext("2d");
|
|
1093
1095
|
if (!ctx) throw new Error("asciifyVideo: could not get 2d context from canvas.");
|
|
@@ -1108,9 +1110,10 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1108
1110
|
video2 = source;
|
|
1109
1111
|
}
|
|
1110
1112
|
if (container) sizeCanvasToContainer(canvas, container, video2.videoWidth / video2.videoHeight);
|
|
1111
|
-
|
|
1112
|
-
const { frames, fps } = await videoToAsciiFrames(video2, merged, canvas.width, canvas.height);
|
|
1113
|
+
const maxDur = trimEnd !== void 0 ? trimEnd - trimStart : 10;
|
|
1114
|
+
const { frames, fps } = await videoToAsciiFrames(video2, merged, canvas.width, canvas.height, void 0, maxDur, void 0, trimStart);
|
|
1113
1115
|
let cancelled2 = false, animId2, i = 0, last = performance.now();
|
|
1116
|
+
let firstFrame2 = true;
|
|
1114
1117
|
const interval = 1e3 / fps;
|
|
1115
1118
|
const tick2 = (now) => {
|
|
1116
1119
|
if (cancelled2) return;
|
|
@@ -1118,6 +1121,10 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1118
1121
|
renderFrameToCanvas(ctx, frames[i], merged, canvas.width, canvas.height);
|
|
1119
1122
|
i = (i + 1) % frames.length;
|
|
1120
1123
|
last = now;
|
|
1124
|
+
if (firstFrame2) {
|
|
1125
|
+
firstFrame2 = false;
|
|
1126
|
+
onReady?.(video2);
|
|
1127
|
+
}
|
|
1121
1128
|
onFrame?.();
|
|
1122
1129
|
}
|
|
1123
1130
|
animId2 = requestAnimationFrame(tick2);
|
|
@@ -1160,6 +1167,23 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1160
1167
|
if (video.paused) await video.play().catch(() => {
|
|
1161
1168
|
});
|
|
1162
1169
|
}
|
|
1170
|
+
if (trimStart > 0) {
|
|
1171
|
+
video.currentTime = trimStart;
|
|
1172
|
+
await new Promise((resolve) => {
|
|
1173
|
+
const h = () => {
|
|
1174
|
+
video.removeEventListener("seeked", h);
|
|
1175
|
+
resolve();
|
|
1176
|
+
};
|
|
1177
|
+
video.addEventListener("seeked", h);
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
let timeupdateHandler = null;
|
|
1181
|
+
if (trimEnd !== void 0) {
|
|
1182
|
+
timeupdateHandler = () => {
|
|
1183
|
+
if (video.currentTime >= trimEnd) video.currentTime = trimStart;
|
|
1184
|
+
};
|
|
1185
|
+
video.addEventListener("timeupdate", timeupdateHandler);
|
|
1186
|
+
}
|
|
1163
1187
|
let ro = null;
|
|
1164
1188
|
if (container) {
|
|
1165
1189
|
const aspect = video.videoWidth / video.videoHeight;
|
|
@@ -1167,9 +1191,9 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1167
1191
|
ro = new ResizeObserver(() => sizeCanvasToContainer(canvas, container, aspect));
|
|
1168
1192
|
ro.observe(container);
|
|
1169
1193
|
}
|
|
1170
|
-
onReady?.(video);
|
|
1171
1194
|
let cancelled = false;
|
|
1172
1195
|
let animId;
|
|
1196
|
+
let firstFrame = true;
|
|
1173
1197
|
const tick = () => {
|
|
1174
1198
|
if (cancelled) return;
|
|
1175
1199
|
animId = requestAnimationFrame(tick);
|
|
@@ -1177,6 +1201,10 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1177
1201
|
const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
|
|
1178
1202
|
if (frame.length > 0) {
|
|
1179
1203
|
renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
|
|
1204
|
+
if (firstFrame) {
|
|
1205
|
+
firstFrame = false;
|
|
1206
|
+
onReady?.(video);
|
|
1207
|
+
}
|
|
1180
1208
|
onFrame?.();
|
|
1181
1209
|
}
|
|
1182
1210
|
};
|
|
@@ -1185,6 +1213,7 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1185
1213
|
cancelled = true;
|
|
1186
1214
|
cancelAnimationFrame(animId);
|
|
1187
1215
|
ro?.disconnect();
|
|
1216
|
+
if (timeupdateHandler) video.removeEventListener("timeupdate", timeupdateHandler);
|
|
1188
1217
|
if (ownedVideo) {
|
|
1189
1218
|
video.pause();
|
|
1190
1219
|
video.src = "";
|