demowright 2.0.9 → 2.2.0

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/config.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as defaultOptions } from "./setup-BwdzChHS.mjs";
1
+ import { n as defaultOptions } from "./setup-Ce4X1N16.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { execSync } from "node:child_process";
4
4
  import { mkdirSync, readdirSync } from "node:fs";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { r as AudioWriter, t as applyHud } from "./setup-BwdzChHS.mjs";
1
+ import { r as AudioWriter, t as applyHud } from "./setup-Ce4X1N16.mjs";
2
2
  import { i as getGlobalTtsProvider, s as init_hud_registry } from "./hud-registry-Wfd4b4Nu.mjs";
3
3
  import { buildFfmpegCommand, createVideoScript, t as init_video_script } from "./video-script.mjs";
4
4
  import { annotate, caption, clickEl, hudWait, moveTo, moveToEl, narrate, subtitle, typeKeys } from "./helpers.mjs";
@@ -622,7 +622,6 @@ function buildAndSaveAudioTrack(segments, outputPath, contextStartMs, browserAud
622
622
  for (const seg of segments) {
623
623
  const dOff = seg.wavBuf.indexOf("data") + 8;
624
624
  if (dOff < 8) continue;
625
- seg.wavBuf.readUInt32LE(24);
626
625
  const ch = seg.wavBuf.readUInt16LE(22);
627
626
  const pcmData = seg.wavBuf.subarray(dOff);
628
627
  const sampleCount = pcmData.length / 2;
package/dist/setup.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as defaultOptions, t as applyHud } from "./setup-BwdzChHS.mjs";
1
+ import { n as defaultOptions, t as applyHud } from "./setup-Ce4X1N16.mjs";
2
2
  export { applyHud, defaultOptions };
@@ -10,6 +10,9 @@ interface TitleStep {
10
10
  kind: "title";
11
11
  text: string;
12
12
  subtitle?: string;
13
+ /** Optional TTS narration read aloud over the card. When set, card
14
+ * duration becomes max(durationMs, narration audio length). */
15
+ narration?: string;
13
16
  durationMs: number;
14
17
  background?: string;
15
18
  }
@@ -27,6 +30,9 @@ interface OutroStep {
27
30
  kind: "outro";
28
31
  text: string;
29
32
  subtitle?: string;
33
+ /** Optional TTS narration read aloud over the card. When set, card
34
+ * duration becomes max(durationMs, narration audio length). */
35
+ narration?: string;
30
36
  durationMs: number;
31
37
  background?: string;
32
38
  }
@@ -53,6 +59,8 @@ interface VideoScriptResult {
53
59
  }
54
60
  interface TitleOptions {
55
61
  subtitle?: string;
62
+ /** TTS narration read aloud over the card overlay. Duration auto-extends to fit. */
63
+ narration?: string;
56
64
  durationMs?: number;
57
65
  /** CSS background — default: radial gradient */
58
66
  background?: string;
@@ -60,6 +68,8 @@ interface TitleOptions {
60
68
  interface OutroOptions {
61
69
  text?: string;
62
70
  subtitle?: string;
71
+ /** TTS narration read aloud over the card overlay. Duration auto-extends to fit. */
72
+ narration?: string;
63
73
  durationMs?: number;
64
74
  background?: string;
65
75
  }
@@ -89,7 +99,7 @@ declare class VideoScriptImpl {
89
99
  private static ttsCache;
90
100
  /**
91
101
  * Add a title card — full-screen overlay with text + optional subtitle.
92
- * No TTS narration by default (silent title card).
102
+ * Pass `narration` in opts to have TTS voiceover during the card.
93
103
  */
94
104
  title(text: string, opts?: TitleOptions): this;
95
105
  /**
@@ -104,6 +114,7 @@ declare class VideoScriptImpl {
104
114
  transition(type?: TransitionType, durationMs?: number): this;
105
115
  /**
106
116
  * Add an outro card — full-screen overlay, similar to title.
117
+ * Pass `narration` in opts to have TTS voiceover during the card.
107
118
  */
108
119
  outro(opts?: OutroOptions): this;
109
120
  /**
@@ -170,8 +170,6 @@ function generateSrt(timeline) {
170
170
  }
171
171
  function generateChapters(timeline) {
172
172
  return timeline.filter((e) => e.kind === "segment" || e.kind === "title" || e.kind === "outro").map((entry) => {
173
- (entry.startMs / 1e3).toFixed(3);
174
- ((entry.startMs + entry.durationMs) / 1e3).toFixed(3);
175
173
  return `[CHAPTER]\nTIMEBASE=1/1000\nSTART=${Math.round(entry.startMs)}\nEND=${Math.round(entry.startMs + entry.durationMs)}\ntitle=${entry.text.slice(0, 80)}`;
176
174
  }).join("\n\n");
177
175
  }
@@ -269,13 +267,14 @@ var init_video_script = __esmMin((() => {
269
267
  static ttsCache = /* @__PURE__ */ new Map();
270
268
  /**
271
269
  * Add a title card — full-screen overlay with text + optional subtitle.
272
- * No TTS narration by default (silent title card).
270
+ * Pass `narration` in opts to have TTS voiceover during the card.
273
271
  */
274
272
  title(text, opts) {
275
273
  this.steps.push({
276
274
  kind: "title",
277
275
  text,
278
276
  subtitle: opts?.subtitle,
277
+ narration: opts?.narration,
279
278
  durationMs: opts?.durationMs ?? 4e3,
280
279
  background: opts?.background
281
280
  });
@@ -307,12 +306,14 @@ var init_video_script = __esmMin((() => {
307
306
  }
308
307
  /**
309
308
  * Add an outro card — full-screen overlay, similar to title.
309
+ * Pass `narration` in opts to have TTS voiceover during the card.
310
310
  */
311
311
  outro(opts) {
312
312
  this.steps.push({
313
313
  kind: "outro",
314
314
  text: opts?.text ?? "Thanks for watching!",
315
315
  subtitle: opts?.subtitle,
316
+ narration: opts?.narration,
316
317
  durationMs: opts?.durationMs ?? 4e3,
317
318
  background: opts?.background
318
319
  });
@@ -328,11 +329,20 @@ var init_video_script = __esmMin((() => {
328
329
  else if (typeof pageOrProvider === "function" || typeof pageOrProvider === "string") provider = pageOrProvider;
329
330
  else provider = getTtsProvider(pageOrProvider);
330
331
  if (!provider) return;
331
- const segmentSteps = this.steps.filter((s) => s.kind === "segment").map((s, i) => ({
332
- ...s,
333
- id: `step-${i}`
334
- }));
335
- const results = await Promise.allSettled(segmentSteps.map(async (step) => {
332
+ const ttsSteps = [];
333
+ let segIdx2 = 0;
334
+ for (let i = 0; i < this.steps.length; i++) {
335
+ const s = this.steps[i];
336
+ if (s.kind === "segment") ttsSteps.push({
337
+ id: `step-${segIdx2++}`,
338
+ text: s.text
339
+ });
340
+ else if ((s.kind === "title" || s.kind === "outro") && s.narration) ttsSteps.push({
341
+ id: `card-${i}`,
342
+ text: s.narration
343
+ });
344
+ }
345
+ const results = await Promise.allSettled(ttsSteps.map(async (step) => {
336
346
  const cached = VideoScriptImpl.ttsCache.get(step.text);
337
347
  if (cached) return {
338
348
  id: step.id,
@@ -354,7 +364,7 @@ var init_video_script = __esmMin((() => {
354
364
  durationMs: r.value.durationMs
355
365
  };
356
366
  this.prepared.set(r.value.id, seg);
357
- const step = segmentSteps[idx];
367
+ const step = ttsSteps[idx];
358
368
  if (step) VideoScriptImpl.ttsCache.set(step.text, seg);
359
369
  }
360
370
  idx++;
@@ -366,7 +376,7 @@ var init_video_script = __esmMin((() => {
366
376
  * for deferred track building at context close (setup.ts).
367
377
  */
368
378
  async run(page) {
369
- const { timeline, totalMs, audioSegments } = await this.executeSteps(page);
379
+ const { timeline, totalMs } = await this.executeSteps(page);
370
380
  return {
371
381
  timeline,
372
382
  totalMs,
@@ -452,14 +462,28 @@ var init_video_script = __esmMin((() => {
452
462
  const stepStartMs = Date.now();
453
463
  if (step.kind === "title" || step.kind === "outro") {
454
464
  const bg = step.background ?? DEFAULT_BG;
455
- if (active) showCard(page, step.text, step.subtitle, step.durationMs, bg).catch(() => {});
456
- await page.waitForTimeout(step.durationMs);
465
+ const cardTts = step.narration ? this.prepared.get(`card-${i}`) : void 0;
466
+ const effectiveDuration = cardTts ? Math.max(step.durationMs, cardTts.durationMs + 500) : step.durationMs;
467
+ if (active) showCard(page, step.text, step.subtitle, effectiveDuration, bg).catch(() => {});
468
+ if (active && cardTts) {
469
+ const offsetMs = Date.now() - planStartMs;
470
+ audioSegments.push({
471
+ offsetMs,
472
+ wavBuf: cardTts.wavBuf
473
+ });
474
+ storeAudioSegment(page, {
475
+ timestampMs: Date.now(),
476
+ wavBuf: cardTts.wavBuf
477
+ });
478
+ showCaption(page, step.narration, cardTts.durationMs).catch(() => {});
479
+ }
480
+ await page.waitForTimeout(effectiveDuration);
457
481
  timeline.push({
458
482
  id: stepIds[i],
459
483
  kind: step.kind,
460
- text: step.text,
484
+ text: step.narration ?? step.text,
461
485
  startMs: stepStartMs - planStartMs,
462
- durationMs: step.durationMs,
486
+ durationMs: effectiveDuration,
463
487
  actionMs: 0,
464
488
  overrunMs: 0
465
489
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demowright",
3
- "version": "2.0.9",
3
+ "version": "2.2.0",
4
4
  "description": "Playwright video production plugin — cursor overlay, keystroke badges, TTS narration, and narration-driven video scripts for test recordings",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,13 +42,17 @@
42
42
  "build": "tsdown",
43
43
  "test": "bunx playwright test",
44
44
  "demo": "bunx playwright test tests/demo.spec.ts --headed",
45
- "prepublishOnly": "tsdown"
45
+ "prepublishOnly": "tsdown",
46
+ "lint": "oxlint src",
47
+ "typecheck": "tsgo --noEmit"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@types/node": "^25.5.2",
49
51
  "@typescript/native-preview": "^7.0.0-dev.20260405.1",
52
+ "oxlint": "^1.59.0",
50
53
  "semantic-release": "^25.0.3",
51
- "tsdown": "^0.21.7"
54
+ "tsdown": "^0.21.7",
55
+ "typescript": "^6.0.2"
52
56
  },
53
57
  "peerDependencies": {
54
58
  "@playwright/test": ">=1.40.0"