playwright-recast 0.1.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +282 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +139 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/filter/step-filter.d.ts +7 -0
  8. package/dist/filter/step-filter.d.ts.map +1 -0
  9. package/dist/filter/step-filter.js +23 -0
  10. package/dist/filter/step-filter.js.map +1 -0
  11. package/dist/helpers.d.ts +41 -0
  12. package/dist/helpers.d.ts.map +1 -0
  13. package/dist/helpers.js +73 -0
  14. package/dist/helpers.js.map +1 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/parse/jsonl-parser.d.ts +87 -0
  20. package/dist/parse/jsonl-parser.d.ts.map +1 -0
  21. package/dist/parse/jsonl-parser.js +17 -0
  22. package/dist/parse/jsonl-parser.js.map +1 -0
  23. package/dist/parse/trace-parser.d.ts +6 -0
  24. package/dist/parse/trace-parser.d.ts.map +1 -0
  25. package/dist/parse/trace-parser.js +149 -0
  26. package/dist/parse/trace-parser.js.map +1 -0
  27. package/dist/parse/zip-reader.d.ts +21 -0
  28. package/dist/parse/zip-reader.d.ts.map +1 -0
  29. package/dist/parse/zip-reader.js +45 -0
  30. package/dist/parse/zip-reader.js.map +1 -0
  31. package/dist/pipeline/executor.d.ts +16 -0
  32. package/dist/pipeline/executor.d.ts.map +1 -0
  33. package/dist/pipeline/executor.js +283 -0
  34. package/dist/pipeline/executor.js.map +1 -0
  35. package/dist/pipeline/pipeline.d.ts +79 -0
  36. package/dist/pipeline/pipeline.d.ts.map +1 -0
  37. package/dist/pipeline/pipeline.js +105 -0
  38. package/dist/pipeline/pipeline.js.map +1 -0
  39. package/dist/pipeline/stages.d.ts +49 -0
  40. package/dist/pipeline/stages.d.ts.map +1 -0
  41. package/dist/pipeline/stages.js +2 -0
  42. package/dist/pipeline/stages.js.map +1 -0
  43. package/dist/render/renderer.d.ts +22 -0
  44. package/dist/render/renderer.d.ts.map +1 -0
  45. package/dist/render/renderer.js +174 -0
  46. package/dist/render/renderer.js.map +1 -0
  47. package/dist/speed/classifiers.d.ts +8 -0
  48. package/dist/speed/classifiers.d.ts.map +1 -0
  49. package/dist/speed/classifiers.js +38 -0
  50. package/dist/speed/classifiers.js.map +1 -0
  51. package/dist/speed/speed-processor.d.ts +9 -0
  52. package/dist/speed/speed-processor.d.ts.map +1 -0
  53. package/dist/speed/speed-processor.js +97 -0
  54. package/dist/speed/speed-processor.js.map +1 -0
  55. package/dist/speed/time-remap.d.ts +12 -0
  56. package/dist/speed/time-remap.d.ts.map +1 -0
  57. package/dist/speed/time-remap.js +44 -0
  58. package/dist/speed/time-remap.js.map +1 -0
  59. package/dist/subtitles/srt-parser.d.ts +4 -0
  60. package/dist/subtitles/srt-parser.d.ts.map +1 -0
  61. package/dist/subtitles/srt-parser.js +28 -0
  62. package/dist/subtitles/srt-parser.js.map +1 -0
  63. package/dist/subtitles/srt-writer.d.ts +4 -0
  64. package/dist/subtitles/srt-writer.d.ts.map +1 -0
  65. package/dist/subtitles/srt-writer.js +28 -0
  66. package/dist/subtitles/srt-writer.js.map +1 -0
  67. package/dist/subtitles/subtitle-generator.d.ts +9 -0
  68. package/dist/subtitles/subtitle-generator.d.ts.map +1 -0
  69. package/dist/subtitles/subtitle-generator.js +30 -0
  70. package/dist/subtitles/subtitle-generator.js.map +1 -0
  71. package/dist/subtitles/vtt-writer.d.ts +4 -0
  72. package/dist/subtitles/vtt-writer.d.ts.map +1 -0
  73. package/dist/subtitles/vtt-writer.js +28 -0
  74. package/dist/subtitles/vtt-writer.js.map +1 -0
  75. package/dist/types/render.d.ts +41 -0
  76. package/dist/types/render.d.ts.map +1 -0
  77. package/dist/types/render.js +17 -0
  78. package/dist/types/render.js.map +1 -0
  79. package/dist/types/speed.d.ts +54 -0
  80. package/dist/types/speed.d.ts.map +1 -0
  81. package/dist/types/speed.js +2 -0
  82. package/dist/types/speed.js.map +1 -0
  83. package/dist/types/subtitle.d.ts +31 -0
  84. package/dist/types/subtitle.d.ts.map +1 -0
  85. package/dist/types/subtitle.js +2 -0
  86. package/dist/types/subtitle.js.map +1 -0
  87. package/dist/types/trace.d.ts +103 -0
  88. package/dist/types/trace.d.ts.map +1 -0
  89. package/dist/types/trace.js +4 -0
  90. package/dist/types/trace.js.map +1 -0
  91. package/dist/types/voiceover.d.ts +42 -0
  92. package/dist/types/voiceover.d.ts.map +1 -0
  93. package/dist/types/voiceover.js +2 -0
  94. package/dist/types/voiceover.js.map +1 -0
  95. package/dist/utils/ffmpeg.d.ts +7 -0
  96. package/dist/utils/ffmpeg.d.ts.map +1 -0
  97. package/dist/utils/ffmpeg.js +24 -0
  98. package/dist/utils/ffmpeg.js.map +1 -0
  99. package/dist/voiceover/providers/elevenlabs.d.ts +12 -0
  100. package/dist/voiceover/providers/elevenlabs.d.ts.map +1 -0
  101. package/dist/voiceover/providers/elevenlabs.js +55 -0
  102. package/dist/voiceover/providers/elevenlabs.js.map +1 -0
  103. package/dist/voiceover/providers/openai.d.ts +14 -0
  104. package/dist/voiceover/providers/openai.d.ts.map +1 -0
  105. package/dist/voiceover/providers/openai.js +53 -0
  106. package/dist/voiceover/providers/openai.js.map +1 -0
  107. package/dist/voiceover/voiceover-processor.d.ts +9 -0
  108. package/dist/voiceover/voiceover-processor.d.ts.map +1 -0
  109. package/dist/voiceover/voiceover-processor.js +88 -0
  110. package/dist/voiceover/voiceover-processor.js.map +1 -0
  111. package/package.json +87 -0
@@ -0,0 +1,87 @@
1
+ /** Raw trace event as parsed from JSONL */
2
+ export type TraceEventRaw = ContextOptionsEvent | BeforeActionEvent | AfterActionEvent | InputEvent | ScreencastFrameEvent | ResourceSnapshotEvent | ConsoleEvent | GenericEvent;
3
+ export interface ContextOptionsEvent {
4
+ type: 'context-options';
5
+ browserName?: string;
6
+ platform?: string;
7
+ options?: {
8
+ viewport?: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ };
13
+ wallTime?: number;
14
+ monotonicTime?: number;
15
+ playwrightVersion?: string;
16
+ }
17
+ export interface BeforeActionEvent {
18
+ type: 'before';
19
+ callId: string;
20
+ stepId?: string;
21
+ title: string;
22
+ class: string;
23
+ method: string;
24
+ params?: Record<string, unknown>;
25
+ startTime: number;
26
+ pageId?: string;
27
+ parentId?: string;
28
+ stack?: unknown;
29
+ }
30
+ export interface AfterActionEvent {
31
+ type: 'after';
32
+ callId: string;
33
+ endTime: number;
34
+ error?: {
35
+ message: string;
36
+ };
37
+ result?: unknown;
38
+ point?: {
39
+ x: number;
40
+ y: number;
41
+ };
42
+ }
43
+ export interface InputEvent {
44
+ type: 'input';
45
+ callId: string;
46
+ point?: {
47
+ x: number;
48
+ y: number;
49
+ };
50
+ }
51
+ export interface ScreencastFrameEvent {
52
+ type: 'screencast-frame';
53
+ pageId: string;
54
+ sha1: string;
55
+ width: number;
56
+ height: number;
57
+ timestamp: number;
58
+ }
59
+ export interface ResourceSnapshotEvent {
60
+ type: 'resource-snapshot';
61
+ snapshot: {
62
+ request: {
63
+ url: string;
64
+ method: string;
65
+ };
66
+ response: {
67
+ status: number;
68
+ mimeType?: string;
69
+ };
70
+ startedDateTime: string;
71
+ time: number;
72
+ _monotonicTime?: number;
73
+ };
74
+ }
75
+ export interface ConsoleEvent {
76
+ type: 'console';
77
+ time: number;
78
+ text?: string;
79
+ pageId?: string;
80
+ }
81
+ export interface GenericEvent {
82
+ type: string;
83
+ [key: string]: unknown;
84
+ }
85
+ /** Parse a JSONL string into typed trace events, skipping malformed lines */
86
+ export declare function parseJsonl(content: string): TraceEventRaw[];
87
+ //# sourceMappingURL=jsonl-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl-parser.d.ts","sourceRoot":"","sources":["../../src/parse/jsonl-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /** Parse a JSONL string into typed trace events, skipping malformed lines */
2
+ export function parseJsonl(content) {
3
+ const events = [];
4
+ for (const line of content.split('\n')) {
5
+ const trimmed = line.trim();
6
+ if (!trimmed)
7
+ continue;
8
+ try {
9
+ events.push(JSON.parse(trimmed));
10
+ }
11
+ catch {
12
+ // Skip malformed lines
13
+ }
14
+ }
15
+ return events;
16
+ }
17
+ //# sourceMappingURL=jsonl-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl-parser.js","sourceRoot":"","sources":["../../src/parse/jsonl-parser.ts"],"names":[],"mappings":"AAoFA,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,MAAM,GAAoB,EAAE,CAAA;IAElC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,OAAO;YAAE,SAAQ;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ParsedTrace } from '../types/trace';
2
+ /**
3
+ * Parse a Playwright trace zip into structured data.
4
+ */
5
+ export declare function parseTrace(tracePath: string): Promise<ParsedTrace>;
6
+ //# sourceMappingURL=trace-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-parser.d.ts","sourceRoot":"","sources":["../../src/parse/trace-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAQZ,MAAM,gBAAgB,CAAA;AAcvB;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiKxE"}
@@ -0,0 +1,149 @@
1
+ import { toMonotonic } from '../types/trace';
2
+ import { ZipReader } from './zip-reader';
3
+ import { parseJsonl, } from './jsonl-parser';
4
+ /**
5
+ * Parse a Playwright trace zip into structured data.
6
+ */
7
+ export async function parseTrace(tracePath) {
8
+ const zip = ZipReader.open(tracePath);
9
+ const entries = zip.entryNames();
10
+ // Parse ALL trace and network JSONL files (multiple contexts in one zip)
11
+ const traceFiles = entries.filter((n) => n.endsWith('.trace'));
12
+ const networkFiles = entries.filter((n) => n.endsWith('.network'));
13
+ const traceEvents = traceFiles.flatMap((f) => parseJsonl(zip.readText(f)));
14
+ const networkEvents = networkFiles.flatMap((f) => parseJsonl(zip.readText(f)));
15
+ // Find the most informative context-options event (one with browserName set)
16
+ const ctxOptsAll = traceEvents.filter((e) => e.type === 'context-options');
17
+ const ctxOpts = ctxOptsAll.find((e) => e.browserName && e.browserName.length > 0) ??
18
+ ctxOptsAll[0];
19
+ // Build action map: before + after events paired by callId
20
+ const actionStarts = new Map();
21
+ const actionEnds = new Map();
22
+ const inputPoints = new Map();
23
+ for (const event of traceEvents) {
24
+ switch (event.type) {
25
+ case 'before': {
26
+ const e = event;
27
+ actionStarts.set(e.callId, e);
28
+ break;
29
+ }
30
+ case 'after': {
31
+ const e = event;
32
+ actionEnds.set(e.callId, e);
33
+ break;
34
+ }
35
+ case 'input': {
36
+ const e = event;
37
+ if (e.point)
38
+ inputPoints.set(e.callId, e.point);
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ // Build actions
44
+ const actions = [];
45
+ for (const [callId, start] of actionStarts) {
46
+ const end = actionEnds.get(callId);
47
+ const point = inputPoints.get(callId);
48
+ actions.push({
49
+ callId,
50
+ stepId: start.stepId,
51
+ title: start.title,
52
+ class: start.class,
53
+ method: start.method,
54
+ params: start.params ?? {},
55
+ startTime: toMonotonic(start.startTime),
56
+ endTime: toMonotonic(end?.endTime ?? start.startTime),
57
+ parentId: start.parentId,
58
+ error: end?.error,
59
+ point: point
60
+ ? { x: point.x, y: point.y, timestamp: toMonotonic(start.startTime) }
61
+ : undefined,
62
+ });
63
+ }
64
+ actions.sort((a, b) => a.startTime - b.startTime);
65
+ // Extract screencast frames
66
+ const frames = traceEvents
67
+ .filter((e) => e.type === 'screencast-frame')
68
+ .map((e) => ({
69
+ sha1: e.sha1,
70
+ timestamp: toMonotonic(e.timestamp),
71
+ pageId: e.pageId,
72
+ width: e.width,
73
+ height: e.height,
74
+ }))
75
+ .sort((a, b) => a.timestamp - b.timestamp);
76
+ // Extract network resources
77
+ const resources = networkEvents
78
+ .filter((e) => e.type === 'resource-snapshot')
79
+ .map((e) => {
80
+ const s = e.snapshot;
81
+ const startTime = s._monotonicTime ?? 0;
82
+ return {
83
+ url: s.request.url,
84
+ method: s.request.method,
85
+ status: s.response.status,
86
+ startTime: toMonotonic(startTime),
87
+ endTime: toMonotonic(startTime + (s.time ?? 0)),
88
+ mimeType: s.response.mimeType ?? '',
89
+ };
90
+ });
91
+ // Extract cursor positions from input events
92
+ const cursorPositions = [];
93
+ for (const [callId, point] of inputPoints) {
94
+ const start = actionStarts.get(callId);
95
+ if (start) {
96
+ cursorPositions.push({
97
+ x: point.x,
98
+ y: point.y,
99
+ timestamp: toMonotonic(start.startTime),
100
+ });
101
+ }
102
+ }
103
+ cursorPositions.sort((a, b) => a.timestamp - b.timestamp);
104
+ // Extract console/page events
105
+ const events = traceEvents
106
+ .filter((e) => e.type === 'console' || e.type === 'event')
107
+ .map((e) => ({
108
+ type: e.type,
109
+ time: toMonotonic(e.time),
110
+ pageId: e.pageId,
111
+ text: e.text,
112
+ }));
113
+ // Compute time boundaries
114
+ const allTimes = [
115
+ ...actions.map((a) => a.startTime),
116
+ ...actions.map((a) => a.endTime),
117
+ ...frames.map((f) => f.timestamp),
118
+ ].filter((t) => t > 0);
119
+ const startTime = allTimes.length > 0 ? Math.min(...allTimes) : 0;
120
+ const endTime = allTimes.length > 0 ? Math.max(...allTimes) : 0;
121
+ // Create frame reader
122
+ const frameReader = {
123
+ readFrame(sha1) {
124
+ const name = `resources/${sha1}`;
125
+ return Promise.resolve(zip.readBinary(name));
126
+ },
127
+ dispose() {
128
+ zip.dispose();
129
+ },
130
+ };
131
+ return {
132
+ metadata: {
133
+ browserName: ctxOpts?.browserName ?? 'unknown',
134
+ platform: ctxOpts?.platform ?? 'unknown',
135
+ viewport: ctxOpts?.options?.viewport ?? { width: 1920, height: 1080 },
136
+ startTime: toMonotonic(startTime),
137
+ endTime: toMonotonic(endTime),
138
+ wallTime: ctxOpts?.wallTime ?? 0,
139
+ playwrightVersion: ctxOpts?.playwrightVersion,
140
+ },
141
+ frames,
142
+ actions,
143
+ resources,
144
+ events,
145
+ cursorPositions,
146
+ frameReader,
147
+ };
148
+ }
149
+ //# sourceMappingURL=trace-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-parser.js","sourceRoot":"","sources":["../../src/parse/trace-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Reads entries from a Playwright trace ZIP file.
3
+ * Uses fflate (pure JS) for zero-dependency ZIP extraction.
4
+ */
5
+ export declare class ZipReader {
6
+ private entries;
7
+ private constructor();
8
+ /** Open a zip file and extract all entries into memory */
9
+ static open(zipPath: string): ZipReader;
10
+ /** Get all entry names in the zip */
11
+ entryNames(): string[];
12
+ /** Read an entry as UTF-8 text */
13
+ readText(name: string): string;
14
+ /** Read an entry as raw binary buffer */
15
+ readBinary(name: string): Buffer;
16
+ /** Check if an entry exists */
17
+ has(name: string): boolean;
18
+ /** Release memory */
19
+ dispose(): void;
20
+ }
21
+ //# sourceMappingURL=zip-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip-reader.d.ts","sourceRoot":"","sources":["../../src/parse/zip-reader.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA4B;IAE3C,OAAO;IAIP,0DAA0D;IAC1D,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS;IAMvC,qCAAqC;IACrC,UAAU,IAAI,MAAM,EAAE;IAItB,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAM9B,yCAAyC;IACzC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMhC,+BAA+B;IAC/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,qBAAqB;IACrB,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,45 @@
1
+ import * as fs from 'node:fs';
2
+ import { unzipSync, strFromU8 } from 'fflate';
3
+ /**
4
+ * Reads entries from a Playwright trace ZIP file.
5
+ * Uses fflate (pure JS) for zero-dependency ZIP extraction.
6
+ */
7
+ export class ZipReader {
8
+ entries;
9
+ constructor(entries) {
10
+ this.entries = entries;
11
+ }
12
+ /** Open a zip file and extract all entries into memory */
13
+ static open(zipPath) {
14
+ const data = fs.readFileSync(zipPath);
15
+ const entries = unzipSync(new Uint8Array(data));
16
+ return new ZipReader(entries);
17
+ }
18
+ /** Get all entry names in the zip */
19
+ entryNames() {
20
+ return Object.keys(this.entries);
21
+ }
22
+ /** Read an entry as UTF-8 text */
23
+ readText(name) {
24
+ const entry = this.entries[name];
25
+ if (!entry)
26
+ throw new Error(`Entry not found in zip: ${name}`);
27
+ return strFromU8(entry);
28
+ }
29
+ /** Read an entry as raw binary buffer */
30
+ readBinary(name) {
31
+ const entry = this.entries[name];
32
+ if (!entry)
33
+ throw new Error(`Entry not found in zip: ${name}`);
34
+ return Buffer.from(entry);
35
+ }
36
+ /** Check if an entry exists */
37
+ has(name) {
38
+ return name in this.entries;
39
+ }
40
+ /** Release memory */
41
+ dispose() {
42
+ this.entries = {};
43
+ }
44
+ }
45
+ //# sourceMappingURL=zip-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip-reader.js","sourceRoot":"","sources":["../../src/parse/zip-reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAE7C;;;GAGG;AACH,MAAM,OAAO,SAAS;IACZ,OAAO,CAA4B;IAE3C,YAAoB,OAAmC;QACrD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,0DAA0D;IAC1D,MAAM,CAAC,IAAI,CAAC,OAAe;QACzB,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/C,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;IAC/B,CAAC;IAED,qCAAqC;IACrC,UAAU;QACR,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,kCAAkC;IAClC,QAAQ,CAAC,IAAY;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAA;QAC9D,OAAO,SAAS,CAAC,KAAK,CAAC,CAAA;IACzB,CAAC;IAED,yCAAyC;IACzC,UAAU,CAAC,IAAY;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAA;QAC9D,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAED,+BAA+B;IAC/B,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,IAAI,IAAI,CAAC,OAAO,CAAA;IAC7B,CAAC;IAED,qBAAqB;IACrB,OAAO;QACL,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;IACnB,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { StageDescriptor } from './stages';
2
+ /**
3
+ * Executes a pipeline by walking through stages sequentially.
4
+ * Each stage transforms the state into the next type in the chain.
5
+ */
6
+ export declare class PipelineExecutor {
7
+ private readonly source;
8
+ private readonly stages;
9
+ constructor(source: string, stages: readonly StageDescriptor[]);
10
+ execute(outputPath: string): Promise<void>;
11
+ executeToBuffer(): Promise<Buffer>;
12
+ private runStages;
13
+ private findTraceZip;
14
+ private findSourceVideo;
15
+ }
16
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/pipeline/executor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAyB/C;;;GAGG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,eAAe,EAAE;IAG/C,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8D1C,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;YAQ1B,SAAS;IA6KvB,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,eAAe;CAwBxB"}
@@ -0,0 +1,283 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { parseTrace } from '../parse/trace-parser';
4
+ import { filterSteps } from '../filter/step-filter';
5
+ import { processSpeed } from '../speed/speed-processor';
6
+ import { generateSubtitles } from '../subtitles/subtitle-generator';
7
+ import { parseSrt } from '../subtitles/srt-parser';
8
+ import { generateVoiceover } from '../voiceover/voiceover-processor';
9
+ import { renderVideo } from '../render/renderer';
10
+ import { writeSrt } from '../subtitles/srt-writer';
11
+ import { writeVtt } from '../subtitles/vtt-writer';
12
+ import { assertFfmpegAvailable } from '../utils/ffmpeg';
13
+ /**
14
+ * Executes a pipeline by walking through stages sequentially.
15
+ * Each stage transforms the state into the next type in the chain.
16
+ */
17
+ export class PipelineExecutor {
18
+ source;
19
+ stages;
20
+ constructor(source, stages) {
21
+ this.source = source;
22
+ this.stages = stages;
23
+ }
24
+ async execute(outputPath) {
25
+ assertFfmpegAvailable();
26
+ const state = await this.runStages();
27
+ const outputDir = path.dirname(outputPath);
28
+ const tmpDir = path.join(outputDir, '.recast-tmp');
29
+ fs.mkdirSync(tmpDir, { recursive: true });
30
+ fs.mkdirSync(outputDir, { recursive: true });
31
+ // Find the render config
32
+ const renderStage = this.stages.find((s) => s.type === 'render');
33
+ const renderConfig = renderStage?.type === 'render' ? renderStage.config : {};
34
+ // Determine the most advanced state for rendering
35
+ const renderableTrace = state.voiceovered ?? state.subtitled ?? state.speedMapped ?? state.filtered ?? state.parsed;
36
+ if (!renderableTrace) {
37
+ throw new Error('Pipeline has no data to render. Did you call .parse()?');
38
+ }
39
+ // Build the renderable trace with source video and optional subtitle/voiceover fields
40
+ const traceWithVideo = {
41
+ ...renderableTrace,
42
+ sourceVideoPath: state.sourceVideoPath,
43
+ subtitles: state.subtitled?.subtitles,
44
+ voiceover: state.voiceovered?.voiceover,
45
+ };
46
+ // Render final video
47
+ renderVideo(traceWithVideo, renderConfig, outputPath, tmpDir);
48
+ // Write subtitle files next to the output
49
+ if (state.subtitled) {
50
+ const baseName = path.basename(outputPath, path.extname(outputPath));
51
+ const srtPath = path.join(outputDir, `${baseName}.srt`);
52
+ const vttPath = path.join(outputDir, `${baseName}.vtt`);
53
+ fs.writeFileSync(srtPath, writeSrt(state.subtitled.subtitles));
54
+ fs.writeFileSync(vttPath, writeVtt(state.subtitled.subtitles));
55
+ }
56
+ // Write report.json
57
+ if (state.parsed) {
58
+ const reportPath = path.join(outputDir, 'report.json');
59
+ const report = {
60
+ scenario: 'playwright-recast output',
61
+ sourceVideo: state.sourceVideoPath,
62
+ actionsCount: state.parsed.actions.length,
63
+ framesCount: state.parsed.frames.length,
64
+ resourcesCount: state.parsed.resources.length,
65
+ subtitlesCount: state.subtitled?.subtitles.length ?? 0,
66
+ voiceoverSegments: state.voiceovered?.voiceover.entries.length ?? 0,
67
+ outputFile: path.basename(outputPath),
68
+ };
69
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n');
70
+ }
71
+ // Cleanup
72
+ fs.rmSync(tmpDir, { recursive: true, force: true });
73
+ // Dispose frame reader
74
+ state.parsed?.frameReader.dispose();
75
+ }
76
+ async executeToBuffer() {
77
+ const tmpOutput = path.join('/tmp', `recast-${Date.now()}.mp4`);
78
+ await this.execute(tmpOutput);
79
+ const buffer = fs.readFileSync(tmpOutput);
80
+ fs.unlinkSync(tmpOutput);
81
+ return buffer;
82
+ }
83
+ async runStages() {
84
+ const state = {};
85
+ // Find source video in the trace directory
86
+ state.sourceVideoPath = this.findSourceVideo();
87
+ for (const stage of this.stages) {
88
+ switch (stage.type) {
89
+ case 'parse': {
90
+ const tracePath = this.findTraceZip();
91
+ state.parsed = await parseTrace(tracePath);
92
+ // Default filter (no-op) for downstream stages
93
+ state.filtered = {
94
+ ...state.parsed,
95
+ originalActions: state.parsed.actions,
96
+ hiddenRanges: [],
97
+ };
98
+ break;
99
+ }
100
+ case 'hideSteps': {
101
+ if (!state.parsed)
102
+ throw new Error('hideSteps() requires parse() first');
103
+ state.filtered = filterSteps(state.parsed, stage.predicate);
104
+ break;
105
+ }
106
+ case 'speedUp': {
107
+ if (!state.filtered)
108
+ throw new Error('speedUp() requires parse() first');
109
+ state.speedMapped = processSpeed(state.filtered, stage.config);
110
+ break;
111
+ }
112
+ case 'subtitles': {
113
+ // If no speed processing, create a pass-through speed map
114
+ if (!state.speedMapped && state.filtered) {
115
+ state.speedMapped = {
116
+ ...state.filtered,
117
+ speedSegments: [],
118
+ timeRemap: (t) => t,
119
+ outputDuration: state.filtered.metadata.endTime - state.filtered.metadata.startTime,
120
+ };
121
+ }
122
+ if (!state.speedMapped)
123
+ throw new Error('subtitles() requires parse() first');
124
+ state.subtitled = generateSubtitles(state.speedMapped, stage.textFn, stage.options);
125
+ break;
126
+ }
127
+ case 'subtitlesFromSrt': {
128
+ // Load subtitles directly from an existing SRT file
129
+ const srtContent = fs.readFileSync(stage.srtPath, 'utf-8');
130
+ const subtitles = parseSrt(srtContent);
131
+ // Promote to SubtitledTrace, filling in any missing intermediate fields
132
+ const srtBase = state.speedMapped ?? state.filtered ?? state.parsed;
133
+ if (!srtBase)
134
+ throw new Error('subtitlesFromSrt() requires parse() first');
135
+ const asFiltered = 'originalActions' in srtBase
136
+ ? srtBase
137
+ : { ...srtBase, originalActions: srtBase.actions, hiddenRanges: [] };
138
+ const asSpeedMapped = 'speedSegments' in srtBase
139
+ ? srtBase
140
+ : { ...asFiltered, speedSegments: [], timeRemap: (t) => t, outputDuration: 0 };
141
+ state.subtitled = { ...asSpeedMapped, subtitles };
142
+ break;
143
+ }
144
+ case 'subtitlesFromTrace': {
145
+ // Generate subtitles from BDD step titles extracted from the parsed trace actions.
146
+ // Uses action.text (BDD step text) or falls back to action.title.
147
+ const traceBase = state.speedMapped ?? state.filtered ?? state.parsed;
148
+ if (!traceBase)
149
+ throw new Error('subtitlesFromTrace() requires parse() first');
150
+ // Ensure we have a SpeedMappedTrace (create pass-through if missing)
151
+ let speedMapped;
152
+ if (state.speedMapped) {
153
+ speedMapped = state.speedMapped;
154
+ }
155
+ else {
156
+ const filtered = state.filtered ?? {
157
+ ...state.parsed,
158
+ originalActions: state.parsed.actions,
159
+ hiddenRanges: [],
160
+ };
161
+ speedMapped = {
162
+ ...filtered,
163
+ speedSegments: [],
164
+ timeRemap: (t) => t,
165
+ outputDuration: filtered.metadata.endTime - filtered.metadata.startTime,
166
+ };
167
+ }
168
+ // Extract BDD step text from trace actions
169
+ const defaultTextFn = (action) => action.text ?? (action.keyword ? `${action.keyword} ${action.title}` : undefined);
170
+ state.subtitled = generateSubtitles(speedMapped, defaultTextFn, stage.options);
171
+ break;
172
+ }
173
+ case 'enrichZoomFromReport': {
174
+ if (!state.subtitled)
175
+ throw new Error('enrichZoomFromReport() requires subtitles() first');
176
+ const reportSteps = stage.steps;
177
+ for (let i = 0; i < Math.min(state.subtitled.subtitles.length, reportSteps.length); i++) {
178
+ const z = reportSteps[i]?.zoom;
179
+ if (z) {
180
+ state.subtitled.subtitles[i].zoom = { x: z.x, y: z.y, level: z.level };
181
+ }
182
+ }
183
+ break;
184
+ }
185
+ case 'autoZoom': {
186
+ if (!state.subtitled)
187
+ throw new Error('autoZoom() requires subtitles() first');
188
+ if (!state.parsed)
189
+ throw new Error('autoZoom() requires parse() first');
190
+ const actionLevel = stage.config.actionLevel ?? 1.5;
191
+ const viewport = state.parsed.metadata.viewport;
192
+ // Use the first screencast frame timestamp as video t=0.
193
+ // This is the most reliable clock reference for the recording context.
194
+ const firstFrameTime = state.parsed.frames.length > 0
195
+ ? state.parsed.frames[0].timestamp
196
+ : state.parsed.metadata.startTime;
197
+ // Find click/fill actions and compute their video-relative time
198
+ const USER_METHODS = new Set(['click', 'fill', 'type', 'press', 'selectOption']);
199
+ const clickActions = state.parsed.actions
200
+ .filter((a) => USER_METHODS.has(a.method))
201
+ .map((a) => ({
202
+ action: a,
203
+ videoTimeSec: (a.startTime - firstFrameTime) / 1000,
204
+ point: a.point,
205
+ }))
206
+ .filter((a) => a.videoTimeSec >= 0);
207
+ for (const subtitle of state.subtitled.subtitles) {
208
+ const subStartSec = subtitle.startMs / 1000;
209
+ const subEndSec = subtitle.endMs / 1000;
210
+ // Find actions with cursor points that fall within this subtitle
211
+ const matching = clickActions.filter((a) => a.videoTimeSec >= subStartSec - 1 && a.videoTimeSec <= subEndSec + 1 && a.point);
212
+ if (matching.length > 0) {
213
+ const best = matching[0];
214
+ subtitle.zoom = {
215
+ x: best.point.x / viewport.width,
216
+ y: best.point.y / viewport.height,
217
+ level: actionLevel,
218
+ };
219
+ }
220
+ }
221
+ break;
222
+ }
223
+ case 'voiceover': {
224
+ if (!state.subtitled)
225
+ throw new Error('voiceover() requires subtitles() first');
226
+ const tmpDir = path.join(path.dirname(state.sourceVideoPath ?? '/tmp'), '.recast-vo-tmp');
227
+ state.voiceovered = await generateVoiceover(state.subtitled, stage.provider, tmpDir);
228
+ break;
229
+ }
230
+ case 'render':
231
+ // Config is read during execute(), not here
232
+ break;
233
+ }
234
+ }
235
+ return state;
236
+ }
237
+ findTraceZip() {
238
+ // Check if source is a zip file directly
239
+ if (this.source.endsWith('.zip') && fs.existsSync(this.source)) {
240
+ return this.source;
241
+ }
242
+ // Search in directory for trace.zip
243
+ if (fs.existsSync(this.source) && fs.statSync(this.source).isDirectory()) {
244
+ const traceZip = path.join(this.source, 'trace.zip');
245
+ if (fs.existsSync(traceZip))
246
+ return traceZip;
247
+ // Search subdirectories
248
+ for (const entry of fs.readdirSync(this.source, { withFileTypes: true })) {
249
+ if (entry.isDirectory()) {
250
+ const subTrace = path.join(this.source, entry.name, 'trace.zip');
251
+ if (fs.existsSync(subTrace))
252
+ return subTrace;
253
+ }
254
+ }
255
+ }
256
+ throw new Error(`No trace.zip found at: ${this.source}`);
257
+ }
258
+ findSourceVideo() {
259
+ const dir = this.source.endsWith('.zip')
260
+ ? path.dirname(this.source)
261
+ : this.source;
262
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
263
+ return undefined;
264
+ // Search for .webm files
265
+ const searchDir = (d) => {
266
+ for (const file of fs.readdirSync(d)) {
267
+ if (file.endsWith('.webm'))
268
+ return path.join(d, file);
269
+ }
270
+ // Check subdirectories
271
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
272
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
273
+ const found = searchDir(path.join(d, entry.name));
274
+ if (found)
275
+ return found;
276
+ }
277
+ }
278
+ return undefined;
279
+ };
280
+ return searchDir(dir);
281
+ }
282
+ }
283
+ //# sourceMappingURL=executor.js.map