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.
- package/LICENSE +21 -0
- package/README.md +282 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +139 -0
- package/dist/cli.js.map +1 -0
- package/dist/filter/step-filter.d.ts +7 -0
- package/dist/filter/step-filter.d.ts.map +1 -0
- package/dist/filter/step-filter.js +23 -0
- package/dist/filter/step-filter.js.map +1 -0
- package/dist/helpers.d.ts +41 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +73 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/parse/jsonl-parser.d.ts +87 -0
- package/dist/parse/jsonl-parser.d.ts.map +1 -0
- package/dist/parse/jsonl-parser.js +17 -0
- package/dist/parse/jsonl-parser.js.map +1 -0
- package/dist/parse/trace-parser.d.ts +6 -0
- package/dist/parse/trace-parser.d.ts.map +1 -0
- package/dist/parse/trace-parser.js +149 -0
- package/dist/parse/trace-parser.js.map +1 -0
- package/dist/parse/zip-reader.d.ts +21 -0
- package/dist/parse/zip-reader.d.ts.map +1 -0
- package/dist/parse/zip-reader.js +45 -0
- package/dist/parse/zip-reader.js.map +1 -0
- package/dist/pipeline/executor.d.ts +16 -0
- package/dist/pipeline/executor.d.ts.map +1 -0
- package/dist/pipeline/executor.js +283 -0
- package/dist/pipeline/executor.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +79 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +105 -0
- package/dist/pipeline/pipeline.js.map +1 -0
- package/dist/pipeline/stages.d.ts +49 -0
- package/dist/pipeline/stages.d.ts.map +1 -0
- package/dist/pipeline/stages.js +2 -0
- package/dist/pipeline/stages.js.map +1 -0
- package/dist/render/renderer.d.ts +22 -0
- package/dist/render/renderer.d.ts.map +1 -0
- package/dist/render/renderer.js +174 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/speed/classifiers.d.ts +8 -0
- package/dist/speed/classifiers.d.ts.map +1 -0
- package/dist/speed/classifiers.js +38 -0
- package/dist/speed/classifiers.js.map +1 -0
- package/dist/speed/speed-processor.d.ts +9 -0
- package/dist/speed/speed-processor.d.ts.map +1 -0
- package/dist/speed/speed-processor.js +97 -0
- package/dist/speed/speed-processor.js.map +1 -0
- package/dist/speed/time-remap.d.ts +12 -0
- package/dist/speed/time-remap.d.ts.map +1 -0
- package/dist/speed/time-remap.js +44 -0
- package/dist/speed/time-remap.js.map +1 -0
- package/dist/subtitles/srt-parser.d.ts +4 -0
- package/dist/subtitles/srt-parser.d.ts.map +1 -0
- package/dist/subtitles/srt-parser.js +28 -0
- package/dist/subtitles/srt-parser.js.map +1 -0
- package/dist/subtitles/srt-writer.d.ts +4 -0
- package/dist/subtitles/srt-writer.d.ts.map +1 -0
- package/dist/subtitles/srt-writer.js +28 -0
- package/dist/subtitles/srt-writer.js.map +1 -0
- package/dist/subtitles/subtitle-generator.d.ts +9 -0
- package/dist/subtitles/subtitle-generator.d.ts.map +1 -0
- package/dist/subtitles/subtitle-generator.js +30 -0
- package/dist/subtitles/subtitle-generator.js.map +1 -0
- package/dist/subtitles/vtt-writer.d.ts +4 -0
- package/dist/subtitles/vtt-writer.d.ts.map +1 -0
- package/dist/subtitles/vtt-writer.js +28 -0
- package/dist/subtitles/vtt-writer.js.map +1 -0
- package/dist/types/render.d.ts +41 -0
- package/dist/types/render.d.ts.map +1 -0
- package/dist/types/render.js +17 -0
- package/dist/types/render.js.map +1 -0
- package/dist/types/speed.d.ts +54 -0
- package/dist/types/speed.d.ts.map +1 -0
- package/dist/types/speed.js +2 -0
- package/dist/types/speed.js.map +1 -0
- package/dist/types/subtitle.d.ts +31 -0
- package/dist/types/subtitle.d.ts.map +1 -0
- package/dist/types/subtitle.js +2 -0
- package/dist/types/subtitle.js.map +1 -0
- package/dist/types/trace.d.ts +103 -0
- package/dist/types/trace.d.ts.map +1 -0
- package/dist/types/trace.js +4 -0
- package/dist/types/trace.js.map +1 -0
- package/dist/types/voiceover.d.ts +42 -0
- package/dist/types/voiceover.d.ts.map +1 -0
- package/dist/types/voiceover.js +2 -0
- package/dist/types/voiceover.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +7 -0
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +24 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/voiceover/providers/elevenlabs.d.ts +12 -0
- package/dist/voiceover/providers/elevenlabs.d.ts.map +1 -0
- package/dist/voiceover/providers/elevenlabs.js +55 -0
- package/dist/voiceover/providers/elevenlabs.js.map +1 -0
- package/dist/voiceover/providers/openai.d.ts +14 -0
- package/dist/voiceover/providers/openai.d.ts.map +1 -0
- package/dist/voiceover/providers/openai.js +53 -0
- package/dist/voiceover/providers/openai.js.map +1 -0
- package/dist/voiceover/voiceover-processor.d.ts +9 -0
- package/dist/voiceover/voiceover-processor.d.ts.map +1 -0
- package/dist/voiceover/voiceover-processor.js +88 -0
- package/dist/voiceover/voiceover-processor.js.map +1 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrik Szewczyk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# playwright-recast
|
|
2
|
+
|
|
3
|
+
**Transform Playwright traces into stunning demo videos — automatically.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/playwright-recast)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
> Your Playwright tests already capture everything — traces, screenshots, network activity, cursor positions. **playwright-recast** turns those artifacts into polished, narrated product videos with a single fluent pipeline.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why?
|
|
15
|
+
|
|
16
|
+
Recording product demos is painful. Every UI change means re-recording. Manual voiceover and subtitling takes hours. Timing is always off.
|
|
17
|
+
|
|
18
|
+
**playwright-recast** flips this:** your Playwright tests become your video source.** Write tests once, regenerate polished videos on every deploy.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Recast, ElevenLabsProvider } from 'playwright-recast'
|
|
22
|
+
|
|
23
|
+
await Recast
|
|
24
|
+
.from('./test-results/trace.zip')
|
|
25
|
+
.parse()
|
|
26
|
+
.speedUp({ duringIdle: 3.0, duringUserAction: 1.0 })
|
|
27
|
+
.subtitlesFromSrt('./narration.srt')
|
|
28
|
+
.voiceover(ElevenLabsProvider({ voiceId: 'daniel' }))
|
|
29
|
+
.render({ format: 'mp4', resolution: '1080p' })
|
|
30
|
+
.toFile('demo.mp4')
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**That's it.** Trace in, polished video out.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Fluent pipeline API** — Chainable, immutable, lazy-evaluated. Build complex pipelines that read like English.
|
|
40
|
+
- **Trace-based processing** — Parses Playwright trace.zip (actions, screenshots, network, cursor positions). No manual recording needed.
|
|
41
|
+
- **Smart speed control** — Automatically speeds up idle time, network waits, and navigation while keeping user actions at normal speed.
|
|
42
|
+
- **TTS voiceover** — Generate narration with OpenAI TTS or ElevenLabs. Properly timed with silence padding.
|
|
43
|
+
- **Subtitle generation** — SRT and WebVTT output. Import external SRT or generate from trace BDD step titles.
|
|
44
|
+
- **playwright-bdd support** — First-class integration with playwright-bdd Gherkin steps. Doc strings become voiceover narration.
|
|
45
|
+
- **Step helpers** — `narrate()`, `zoom()`, `pace()` — importable helpers for Playwright step definitions.
|
|
46
|
+
- **CLI included** — `npx playwright-recast -i trace.zip -o demo.mp4` — no code needed.
|
|
47
|
+
- **Zero lock-in** — Every stage is optional. Use just the trace parser, just the subtitle generator, or the full pipeline.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
### Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install playwright-recast
|
|
57
|
+
# or
|
|
58
|
+
bun add playwright-recast
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**System requirement:** `ffmpeg` and `ffprobe` must be on your PATH.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# macOS
|
|
65
|
+
brew install ffmpeg
|
|
66
|
+
|
|
67
|
+
# Ubuntu
|
|
68
|
+
sudo apt install ffmpeg
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### CLI Usage
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Basic — trace to video
|
|
75
|
+
npx playwright-recast -i ./test-results/trace.zip -o demo.mp4
|
|
76
|
+
|
|
77
|
+
# With speed processing
|
|
78
|
+
npx playwright-recast -i ./traces --speed-idle 4.0 --speed-action 1.0
|
|
79
|
+
|
|
80
|
+
# With external SRT subtitles
|
|
81
|
+
npx playwright-recast -i ./traces --srt narration.srt --burn-subs
|
|
82
|
+
|
|
83
|
+
# With TTS voiceover (OpenAI)
|
|
84
|
+
npx playwright-recast -i ./traces --srt narration.srt --provider openai --voice nova
|
|
85
|
+
|
|
86
|
+
# With TTS voiceover (ElevenLabs)
|
|
87
|
+
npx playwright-recast -i ./traces --srt narration.srt --provider elevenlabs --voice onwK4e9ZLuTAKqWW03F9
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Programmatic API
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Recast, OpenAIProvider } from 'playwright-recast'
|
|
94
|
+
|
|
95
|
+
// Minimal — just trace to video
|
|
96
|
+
await Recast.from('./traces').parse().render().toFile('output.mp4')
|
|
97
|
+
|
|
98
|
+
// Full pipeline
|
|
99
|
+
await Recast
|
|
100
|
+
.from('./test-results/')
|
|
101
|
+
.parse()
|
|
102
|
+
.hideSteps(s => s.keyword === 'Given' && s.text?.includes('logged in'))
|
|
103
|
+
.speedUp({
|
|
104
|
+
duringIdle: 4.0,
|
|
105
|
+
duringUserAction: 1.0,
|
|
106
|
+
duringNetworkWait: 2.0,
|
|
107
|
+
minSegmentDuration: 500,
|
|
108
|
+
})
|
|
109
|
+
.subtitlesFromSrt('./narration.srt')
|
|
110
|
+
.voiceover(OpenAIProvider({
|
|
111
|
+
voice: 'nova',
|
|
112
|
+
speed: 1.2,
|
|
113
|
+
instructions: 'Professional product demo narration.',
|
|
114
|
+
}))
|
|
115
|
+
.render({ format: 'mp4', resolution: '1080p', burnSubtitles: true })
|
|
116
|
+
.toFile('demo.mp4')
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### playwright-bdd Integration
|
|
120
|
+
|
|
121
|
+
Use `narrate()` and `pace()` in your BDD step definitions:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// steps/fixtures.ts
|
|
125
|
+
import { test } from 'playwright-bdd'
|
|
126
|
+
import { setupRecast, narrate, pace } from 'playwright-recast'
|
|
127
|
+
|
|
128
|
+
setupRecast(test)
|
|
129
|
+
export { narrate, pace }
|
|
130
|
+
|
|
131
|
+
// steps/my-steps.ts
|
|
132
|
+
import { Given, When, Then } from './fixtures'
|
|
133
|
+
import { narrate, pace } from 'playwright-recast'
|
|
134
|
+
|
|
135
|
+
Given('the user opens the dashboard', async ({ page }, docString?: string) => {
|
|
136
|
+
narrate(docString)
|
|
137
|
+
await page.goto('/dashboard')
|
|
138
|
+
await pace(page, 4000)
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```gherkin
|
|
143
|
+
Feature: Dashboard demo
|
|
144
|
+
|
|
145
|
+
Scenario: View analytics
|
|
146
|
+
Given the user opens the dashboard
|
|
147
|
+
"""
|
|
148
|
+
Let's open the analytics dashboard to see real-time metrics.
|
|
149
|
+
"""
|
|
150
|
+
When the user clicks the revenue chart
|
|
151
|
+
"""
|
|
152
|
+
Clicking on the revenue chart reveals detailed breakdown.
|
|
153
|
+
"""
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Pipeline Stages
|
|
159
|
+
|
|
160
|
+
Every stage is optional and composable:
|
|
161
|
+
|
|
162
|
+
| Stage | Description |
|
|
163
|
+
|-------|-------------|
|
|
164
|
+
| `.parse()` | Parse Playwright trace.zip into structured data (actions, frames, network, cursor) |
|
|
165
|
+
| `.hideSteps(predicate)` | Remove steps from the output (e.g., login, setup) |
|
|
166
|
+
| `.speedUp(config)` | Adjust video speed based on activity (idle, action, network) |
|
|
167
|
+
| `.subtitles(textFn)` | Generate subtitles from trace actions |
|
|
168
|
+
| `.subtitlesFromSrt(path)` | Load subtitles from an external SRT file |
|
|
169
|
+
| `.subtitlesFromTrace()` | Auto-generate subtitles from BDD step titles in trace |
|
|
170
|
+
| `.voiceover(provider)` | Generate TTS audio from subtitle text |
|
|
171
|
+
| `.render(config)` | Render final video (format, resolution, burn subtitles) |
|
|
172
|
+
| `.toFile(path)` | Execute pipeline and write output |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## TTS Providers
|
|
177
|
+
|
|
178
|
+
### OpenAI TTS
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { OpenAIProvider } from 'playwright-recast/providers/openai'
|
|
182
|
+
|
|
183
|
+
OpenAIProvider({
|
|
184
|
+
voice: 'nova', // alloy, echo, fable, onyx, nova, shimmer
|
|
185
|
+
model: 'gpt-4o-mini-tts',
|
|
186
|
+
speed: 1.2,
|
|
187
|
+
instructions: 'Calm, professional demo narration.',
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Requires `OPENAI_API_KEY` environment variable or `apiKey` option.
|
|
192
|
+
|
|
193
|
+
### ElevenLabs
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { ElevenLabsProvider } from 'playwright-recast/providers/elevenlabs'
|
|
197
|
+
|
|
198
|
+
ElevenLabsProvider({
|
|
199
|
+
voiceId: 'onwK4e9ZLuTAKqWW03F9', // Daniel
|
|
200
|
+
modelId: 'eleven_multilingual_v2',
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Requires `ELEVENLABS_API_KEY` environment variable or `apiKey` option.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Speed Processing
|
|
209
|
+
|
|
210
|
+
The speed processor classifies every moment of the trace:
|
|
211
|
+
|
|
212
|
+
| Activity | Default Speed | Description |
|
|
213
|
+
|----------|---------------|-------------|
|
|
214
|
+
| **User Action** | 1.0x | Clicks, fills, keyboard input — real-time |
|
|
215
|
+
| **Navigation** | 2.0x | Page loads, redirects — slightly faster |
|
|
216
|
+
| **Network Wait** | 2.0x | API calls in flight — compress wait time |
|
|
217
|
+
| **Idle** | 4.0x | Nothing happening — skip quickly |
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
.speedUp({
|
|
221
|
+
duringIdle: 4.0,
|
|
222
|
+
duringUserAction: 1.0,
|
|
223
|
+
duringNetworkWait: 2.0,
|
|
224
|
+
duringNavigation: 2.0,
|
|
225
|
+
minSegmentDuration: 500, // Avoid jarring speed changes
|
|
226
|
+
maxSpeed: 8.0, // Safety cap
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Architecture
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
Trace.zip → ParsedTrace → FilteredTrace → SpeedMappedTrace → SubtitledTrace → VoiceoveredTrace → MP4
|
|
236
|
+
↑ ↑ ↑ ↑ ↑ ↑
|
|
237
|
+
parse() hideSteps() speedUp() subtitles() voiceover() render()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The pipeline is **lazy** — calling chain methods builds a pipeline description. Nothing executes until `.toFile()` or `.toBuffer()` is called.
|
|
241
|
+
|
|
242
|
+
Each pipeline instance is **immutable** — every method returns a new pipeline, so you can branch:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const base = Recast.from('./traces').parse().speedUp({ duringIdle: 3.0 })
|
|
246
|
+
|
|
247
|
+
// Branch A: with voiceover
|
|
248
|
+
await base.subtitlesFromSrt('./en.srt').voiceover(openai).render().toFile('demo-en.mp4')
|
|
249
|
+
|
|
250
|
+
// Branch B: subtitles only
|
|
251
|
+
await base.subtitlesFromSrt('./cs.srt').render({ burnSubtitles: true }).toFile('demo-cs.mp4')
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Roadmap
|
|
257
|
+
|
|
258
|
+
- [ ] **Smooth zoom transitions** — Animated crop-and-zoom on elements during specific steps
|
|
259
|
+
- [ ] **Edge TTS provider** — Free TTS without API key using Microsoft Edge's online voices
|
|
260
|
+
- [ ] **Playwright Reporter plugin** — Auto-generate demo videos as part of your test run
|
|
261
|
+
- [ ] **Multi-language support** — Generate video variants from the same trace with different SRT/voiceover per language
|
|
262
|
+
- [ ] **Intro/outro cards** — Configurable title cards, branding overlays, and end screens
|
|
263
|
+
- [ ] **Background music** — Mix ambient music track under voiceover with auto-ducking
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Contributing
|
|
268
|
+
|
|
269
|
+
Contributions welcome! Please check the [issues](https://github.com/ThePatriczek/playwright-recast/issues) for open tasks.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
git clone https://github.com/ThePatriczek/playwright-recast.git
|
|
273
|
+
cd playwright-recast
|
|
274
|
+
npm install
|
|
275
|
+
npm test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { Pipeline as Recast } from './pipeline/pipeline';
|
|
4
|
+
const help = `
|
|
5
|
+
playwright-recast — Convert Playwright traces to polished demo videos
|
|
6
|
+
|
|
7
|
+
USAGE
|
|
8
|
+
playwright-recast --input <trace-dir-or-zip> [options]
|
|
9
|
+
|
|
10
|
+
OPTIONS
|
|
11
|
+
-i, --input Trace directory or zip path (required)
|
|
12
|
+
-o, --output Output file path (default: ./recast-output.mp4)
|
|
13
|
+
--srt External SRT subtitle file path
|
|
14
|
+
--speed-idle Speed for idle periods (default: 3.0)
|
|
15
|
+
--speed-action Speed for user actions (default: 1.0)
|
|
16
|
+
--speed-network Speed for network waits (default: 2.0)
|
|
17
|
+
--no-speed Disable speed processing
|
|
18
|
+
--provider TTS provider: openai | elevenlabs | none (default: none)
|
|
19
|
+
--voice Voice ID for TTS provider
|
|
20
|
+
--model Model ID for TTS provider
|
|
21
|
+
--tts-speed TTS speech speed multiplier
|
|
22
|
+
--format Output format: mp4 | webm (default: mp4)
|
|
23
|
+
--resolution Output resolution: 720p | 1080p (default: 1080p)
|
|
24
|
+
--burn-subs Burn subtitles into video
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
EXAMPLES
|
|
28
|
+
playwright-recast -i ./test-results -o demo.mp4
|
|
29
|
+
playwright-recast -i trace.zip --speed-idle 4 --burn-subs
|
|
30
|
+
playwright-recast -i ./traces --srt narration.srt --provider openai --voice nova
|
|
31
|
+
`.trim();
|
|
32
|
+
function fatal(message) {
|
|
33
|
+
console.error(`Error: ${message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
async function main() {
|
|
37
|
+
const { values } = parseArgs({
|
|
38
|
+
options: {
|
|
39
|
+
input: { type: 'string', short: 'i' },
|
|
40
|
+
output: { type: 'string', short: 'o' },
|
|
41
|
+
srt: { type: 'string' },
|
|
42
|
+
'speed-idle': { type: 'string' },
|
|
43
|
+
'speed-action': { type: 'string' },
|
|
44
|
+
'speed-network': { type: 'string' },
|
|
45
|
+
'no-speed': { type: 'boolean', default: false },
|
|
46
|
+
provider: { type: 'string' },
|
|
47
|
+
voice: { type: 'string' },
|
|
48
|
+
model: { type: 'string' },
|
|
49
|
+
'tts-speed': { type: 'string' },
|
|
50
|
+
format: { type: 'string' },
|
|
51
|
+
resolution: { type: 'string' },
|
|
52
|
+
'burn-subs': { type: 'boolean', default: false },
|
|
53
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
54
|
+
},
|
|
55
|
+
strict: true,
|
|
56
|
+
allowPositionals: false,
|
|
57
|
+
});
|
|
58
|
+
if (values.help) {
|
|
59
|
+
console.log(help);
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
const input = values.input;
|
|
63
|
+
if (!input) {
|
|
64
|
+
fatal('--input / -i is required. Use --help for usage information.');
|
|
65
|
+
}
|
|
66
|
+
const output = values.output ?? './recast-output.mp4';
|
|
67
|
+
const format = values.format ?? 'mp4';
|
|
68
|
+
const resolution = values.resolution ?? '1080p';
|
|
69
|
+
const burnSubtitles = values['burn-subs'] ?? false;
|
|
70
|
+
// Build the pipeline
|
|
71
|
+
let pipeline = Recast.from(input).parse();
|
|
72
|
+
// Speed processing
|
|
73
|
+
if (!values['no-speed']) {
|
|
74
|
+
const speedIdle = values['speed-idle'] ? Number(values['speed-idle']) : 3.0;
|
|
75
|
+
const speedAction = values['speed-action'] ? Number(values['speed-action']) : 1.0;
|
|
76
|
+
const speedNetwork = values['speed-network'] ? Number(values['speed-network']) : 2.0;
|
|
77
|
+
if (Number.isNaN(speedIdle))
|
|
78
|
+
fatal('--speed-idle must be a number');
|
|
79
|
+
if (Number.isNaN(speedAction))
|
|
80
|
+
fatal('--speed-action must be a number');
|
|
81
|
+
if (Number.isNaN(speedNetwork))
|
|
82
|
+
fatal('--speed-network must be a number');
|
|
83
|
+
pipeline = pipeline.speedUp({
|
|
84
|
+
duringIdle: speedIdle,
|
|
85
|
+
duringUserAction: speedAction,
|
|
86
|
+
duringNetworkWait: speedNetwork,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Subtitles
|
|
90
|
+
if (values.srt) {
|
|
91
|
+
pipeline = pipeline.subtitlesFromSrt(values.srt);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
pipeline = pipeline.subtitles((action) => action.docString ?? action.text);
|
|
95
|
+
}
|
|
96
|
+
// Voiceover
|
|
97
|
+
const providerName = values.provider ?? 'none';
|
|
98
|
+
if (providerName !== 'none') {
|
|
99
|
+
const ttsSpeed = values['tts-speed'] ? Number(values['tts-speed']) : undefined;
|
|
100
|
+
if (values['tts-speed'] && Number.isNaN(ttsSpeed)) {
|
|
101
|
+
fatal('--tts-speed must be a number');
|
|
102
|
+
}
|
|
103
|
+
if (providerName === 'openai') {
|
|
104
|
+
const { OpenAIProvider } = await import('./voiceover/providers/openai');
|
|
105
|
+
pipeline = pipeline.voiceover(OpenAIProvider({
|
|
106
|
+
voice: values.voice,
|
|
107
|
+
model: values.model,
|
|
108
|
+
speed: ttsSpeed,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
else if (providerName === 'elevenlabs') {
|
|
112
|
+
const { ElevenLabsProvider } = await import('./voiceover/providers/elevenlabs');
|
|
113
|
+
pipeline = pipeline.voiceover(ElevenLabsProvider({
|
|
114
|
+
voiceId: values.voice,
|
|
115
|
+
modelId: values.model,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
fatal(`Unknown provider: ${providerName}. Use openai, elevenlabs, or none.`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Render
|
|
123
|
+
pipeline = pipeline.render({
|
|
124
|
+
format,
|
|
125
|
+
resolution,
|
|
126
|
+
burnSubtitles,
|
|
127
|
+
});
|
|
128
|
+
// Execute
|
|
129
|
+
console.log(`Processing trace: ${input}`);
|
|
130
|
+
console.log(`Output: ${output} (${format}, ${resolution})`);
|
|
131
|
+
await pipeline.toFile(output);
|
|
132
|
+
console.log(`Done! Video saved to: ${output}`);
|
|
133
|
+
}
|
|
134
|
+
main().catch((error) => {
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
console.error(`Fatal error: ${message}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ParsedTrace, FilteredTrace, TraceAction } from '../types/trace';
|
|
2
|
+
/**
|
|
3
|
+
* Filter out actions matching the predicate.
|
|
4
|
+
* Hidden actions are removed from the action list and their time ranges are recorded.
|
|
5
|
+
*/
|
|
6
|
+
export declare function filterSteps(trace: ParsedTrace, predicate: (action: TraceAction) => boolean): FilteredTrace;
|
|
7
|
+
//# sourceMappingURL=step-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-filter.d.ts","sourceRoot":"","sources":["../../src/filter/step-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAe,MAAM,gBAAgB,CAAA;AAE1F;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,GAC1C,aAAa,CAkBf"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter out actions matching the predicate.
|
|
3
|
+
* Hidden actions are removed from the action list and their time ranges are recorded.
|
|
4
|
+
*/
|
|
5
|
+
export function filterSteps(trace, predicate) {
|
|
6
|
+
const hidden = [];
|
|
7
|
+
const visible = [];
|
|
8
|
+
for (const action of trace.actions) {
|
|
9
|
+
if (predicate(action)) {
|
|
10
|
+
hidden.push({ start: action.startTime, end: action.endTime });
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
visible.push(action);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
...trace,
|
|
18
|
+
originalActions: trace.actions,
|
|
19
|
+
actions: visible,
|
|
20
|
+
hiddenRanges: hidden,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=step-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-filter.js","sourceRoot":"","sources":["../../src/filter/step-filter.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,KAAkB,EAClB,SAA2C;IAE3C,MAAM,MAAM,GAAoD,EAAE,CAAA;IAClE,MAAM,OAAO,GAAkB,EAAE,CAAA;IAEjC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,KAAK;QACR,eAAe,EAAE,KAAK,CAAC,OAAO;QAC9B,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE,MAAM;KACrB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Page, Locator, TestInfo } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Initialize playwright-recast helpers with the test instance.
|
|
4
|
+
* Call once in your fixtures file:
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { test } from 'playwright-bdd'
|
|
8
|
+
* import { setupRecast } from '@workspace/playwright-recast/helpers'
|
|
9
|
+
* setupRecast(test)
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function setupRecast(testInstance: {
|
|
13
|
+
info: () => TestInfo;
|
|
14
|
+
}): void;
|
|
15
|
+
/**
|
|
16
|
+
* Attach voiceover narration text to the current step.
|
|
17
|
+
* Call from EVERY step definition (even without doc string) so
|
|
18
|
+
* the reporter can match annotations to steps by sequential index.
|
|
19
|
+
*
|
|
20
|
+
* @param text Doc string content (voiceover text). Pass undefined if none.
|
|
21
|
+
* @param opts.hidden Mark step as hidden (not recorded, excluded from SRT)
|
|
22
|
+
*/
|
|
23
|
+
export declare function narrate(text: string | undefined, opts?: {
|
|
24
|
+
hidden?: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
/**
|
|
27
|
+
* Zoom into a Playwright element during this step.
|
|
28
|
+
* Gets the element's bounding box and stores relative coordinates as annotation.
|
|
29
|
+
* The renderer applies crop+scale during this step's time window.
|
|
30
|
+
*
|
|
31
|
+
* @param locator Playwright Locator to zoom into
|
|
32
|
+
* @param level Zoom level (1.0 = no zoom, 1.5 = 1.5x closer)
|
|
33
|
+
*/
|
|
34
|
+
export declare function zoom(locator: Locator, level?: number): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Ensure a demo step takes at least `ms` milliseconds.
|
|
37
|
+
* Call at the END of a step to pad with a visual pause
|
|
38
|
+
* so the video has enough time for voiceover narration.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pace(page: Page, ms: number): Promise<void>;
|
|
41
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAgB/D;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;CAAE,GAAG,IAAI,CAExE;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1B,IAAI,CAUN;AAED;;;;;;;GAOG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,OAAO,EAChB,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE"}
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get current test info — works in both playwright-bdd and standard Playwright.
|
|
3
|
+
* Must be called from within a test context.
|
|
4
|
+
*/
|
|
5
|
+
function getTestInfo() {
|
|
6
|
+
// @playwright/test provides test.info() but we can't import 'test' generically.
|
|
7
|
+
// Instead, we use the global __test_info__ or accept it as parameter.
|
|
8
|
+
throw new Error('getTestInfo() must be overridden. Call Recast.setup(test) first.');
|
|
9
|
+
}
|
|
10
|
+
let _getTestInfo = getTestInfo;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize playwright-recast helpers with the test instance.
|
|
13
|
+
* Call once in your fixtures file:
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { test } from 'playwright-bdd'
|
|
17
|
+
* import { setupRecast } from '@workspace/playwright-recast/helpers'
|
|
18
|
+
* setupRecast(test)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function setupRecast(testInstance) {
|
|
22
|
+
_getTestInfo = () => testInstance.info();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Attach voiceover narration text to the current step.
|
|
26
|
+
* Call from EVERY step definition (even without doc string) so
|
|
27
|
+
* the reporter can match annotations to steps by sequential index.
|
|
28
|
+
*
|
|
29
|
+
* @param text Doc string content (voiceover text). Pass undefined if none.
|
|
30
|
+
* @param opts.hidden Mark step as hidden (not recorded, excluded from SRT)
|
|
31
|
+
*/
|
|
32
|
+
export function narrate(text, opts) {
|
|
33
|
+
const hidden = opts?.hidden ?? text?.includes('@hidden') ?? false;
|
|
34
|
+
const cleanText = text?.replace(/@hidden\s*/g, '').trim() || '';
|
|
35
|
+
const info = _getTestInfo();
|
|
36
|
+
info.annotations.push({ type: 'voiceover', description: cleanText });
|
|
37
|
+
info.annotations.push({
|
|
38
|
+
type: 'voiceover-hidden',
|
|
39
|
+
description: hidden ? '1' : '0',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Zoom into a Playwright element during this step.
|
|
44
|
+
* Gets the element's bounding box and stores relative coordinates as annotation.
|
|
45
|
+
* The renderer applies crop+scale during this step's time window.
|
|
46
|
+
*
|
|
47
|
+
* @param locator Playwright Locator to zoom into
|
|
48
|
+
* @param level Zoom level (1.0 = no zoom, 1.5 = 1.5x closer)
|
|
49
|
+
*/
|
|
50
|
+
export async function zoom(locator, level = 1.5) {
|
|
51
|
+
const page = locator.page();
|
|
52
|
+
const viewport = page.viewportSize();
|
|
53
|
+
if (!viewport)
|
|
54
|
+
return;
|
|
55
|
+
const box = await locator.boundingBox();
|
|
56
|
+
if (!box)
|
|
57
|
+
return;
|
|
58
|
+
const x = (box.x + box.width / 2) / viewport.width;
|
|
59
|
+
const y = (box.y + box.height / 2) / viewport.height;
|
|
60
|
+
_getTestInfo().annotations.push({
|
|
61
|
+
type: 'zoom',
|
|
62
|
+
description: JSON.stringify({ x, y, level }),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Ensure a demo step takes at least `ms` milliseconds.
|
|
67
|
+
* Call at the END of a step to pad with a visual pause
|
|
68
|
+
* so the video has enough time for voiceover narration.
|
|
69
|
+
*/
|
|
70
|
+
export async function pace(page, ms) {
|
|
71
|
+
await page.waitForTimeout(ms);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,SAAS,WAAW;IAClB,gFAAgF;IAChF,sEAAsE;IACtE,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAA;AACH,CAAC;AAED,IAAI,YAAY,GAAmB,WAAW,CAAA;AAE9C;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,YAAsC;IAChE,YAAY,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;AAC1C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CACrB,IAAwB,EACxB,IAA2B;IAE3B,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,KAAK,CAAA;IACjE,MAAM,SAAS,GAAG,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;IAE/D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAA;IAC3B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;IACpE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QACpB,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAChC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAAgB,EAChB,QAAgB,GAAG;IAEnB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAA;IAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;IACpC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;IACvC,IAAI,CAAC,GAAG;QAAE,OAAM;IAEhB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAA;IAClD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAA;IAEpD,YAAY,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;QAC9B,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;KAC7C,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU,EAAE,EAAU;IAC/C,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { Pipeline as Recast } from './pipeline/pipeline';
|
|
2
|
+
export { setupRecast, narrate, zoom, pace } from './helpers';
|
|
3
|
+
export { OpenAIProvider } from './voiceover/providers/openai';
|
|
4
|
+
export { ElevenLabsProvider } from './voiceover/providers/elevenlabs';
|
|
5
|
+
export type { ParsedTrace, FilteredTrace, TraceAction, TraceAnnotation, KnownAnnotationType, TraceResource, TraceEvent, ScreencastFrame, CursorPosition, FrameReader, MonotonicMs, } from './types/trace';
|
|
6
|
+
export type { SpeedConfig, SpeedSegment, SpeedRule, SpeedRuleContext, ActivityType, TimeRemapFn, SpeedMappedTrace, } from './types/speed';
|
|
7
|
+
export type { SubtitleEntry, SubtitleFormat, SubtitleOptions, SubtitledTrace, } from './types/subtitle';
|
|
8
|
+
export type { TtsProvider, TtsOptions, AudioSegment, VoiceoverEntry, VoiceoveredTrace, } from './types/voiceover';
|
|
9
|
+
export type { RenderConfig, SubtitleStyle, ZoomKeyframe } from './types/render';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGxD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAGrE,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,EACd,WAAW,EACX,WAAW,GACZ,MAAM,eAAe,CAAA;AAEtB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAEtB,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAE1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public API
|
|
2
|
+
export { Pipeline as Recast } from './pipeline/pipeline';
|
|
3
|
+
// Step helpers (narrate, zoom, pace)
|
|
4
|
+
export { setupRecast, narrate, zoom, pace } from './helpers';
|
|
5
|
+
// Providers
|
|
6
|
+
export { OpenAIProvider } from './voiceover/providers/openai';
|
|
7
|
+
export { ElevenLabsProvider } from './voiceover/providers/elevenlabs';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,QAAQ,IAAI,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAExD,qCAAqC;AACrC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE5D,YAAY;AACZ,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA"}
|