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.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
- onReady?.(video2);
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 = "";