screenwright 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/assets/click-ripple.svg +3 -0
- package/assets/cursor-default.svg +10 -0
- package/assets/cursor-pointer.svg +40 -0
- package/dist/bin/screenwright.d.ts +3 -0
- package/dist/bin/screenwright.d.ts.map +1 -0
- package/dist/bin/screenwright.js +18 -0
- package/dist/bin/screenwright.js.map +1 -0
- package/dist/src/commands/compose.d.ts +3 -0
- package/dist/src/commands/compose.d.ts.map +1 -0
- package/dist/src/commands/compose.js +154 -0
- package/dist/src/commands/compose.js.map +1 -0
- package/dist/src/commands/generate.d.ts +3 -0
- package/dist/src/commands/generate.d.ts.map +1 -0
- package/dist/src/commands/generate.js +70 -0
- package/dist/src/commands/generate.js.map +1 -0
- package/dist/src/commands/init.d.ts +3 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +49 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/preview.d.ts +3 -0
- package/dist/src/commands/preview.d.ts.map +1 -0
- package/dist/src/commands/preview.js +62 -0
- package/dist/src/commands/preview.js.map +1 -0
- package/dist/src/composition/CursorOverlay.d.ts +11 -0
- package/dist/src/composition/CursorOverlay.d.ts.map +1 -0
- package/dist/src/composition/CursorOverlay.js +29 -0
- package/dist/src/composition/CursorOverlay.js.map +1 -0
- package/dist/src/composition/DemoVideo.d.ts +8 -0
- package/dist/src/composition/DemoVideo.d.ts.map +1 -0
- package/dist/src/composition/DemoVideo.js +18 -0
- package/dist/src/composition/DemoVideo.js.map +1 -0
- package/dist/src/composition/NarrationTrack.d.ts +9 -0
- package/dist/src/composition/NarrationTrack.d.ts.map +1 -0
- package/dist/src/composition/NarrationTrack.js +8 -0
- package/dist/src/composition/NarrationTrack.js.map +1 -0
- package/dist/src/composition/cursor-path.d.ts +31 -0
- package/dist/src/composition/cursor-path.d.ts.map +1 -0
- package/dist/src/composition/cursor-path.js +78 -0
- package/dist/src/composition/cursor-path.js.map +1 -0
- package/dist/src/composition/remotion-root.d.ts +3 -0
- package/dist/src/composition/remotion-root.d.ts.map +1 -0
- package/dist/src/composition/remotion-root.js +34 -0
- package/dist/src/composition/remotion-root.js.map +1 -0
- package/dist/src/composition/render.d.ts +8 -0
- package/dist/src/composition/render.d.ts.map +1 -0
- package/dist/src/composition/render.js +24 -0
- package/dist/src/composition/render.js.map +1 -0
- package/dist/src/config/config-schema.d.ts +40 -0
- package/dist/src/config/config-schema.d.ts.map +1 -0
- package/dist/src/config/config-schema.js +13 -0
- package/dist/src/config/config-schema.js.map +1 -0
- package/dist/src/config/defaults.d.ts +4 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +17 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/generator/prompts.d.ts +3 -0
- package/dist/src/generator/prompts.d.ts.map +1 -0
- package/dist/src/generator/prompts.js +125 -0
- package/dist/src/generator/prompts.js.map +1 -0
- package/dist/src/generator/scenario-generator.d.ts +46 -0
- package/dist/src/generator/scenario-generator.d.ts.map +1 -0
- package/dist/src/generator/scenario-generator.js +86 -0
- package/dist/src/generator/scenario-generator.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/runtime/action-helpers.d.ts +19 -0
- package/dist/src/runtime/action-helpers.d.ts.map +1 -0
- package/dist/src/runtime/action-helpers.js +138 -0
- package/dist/src/runtime/action-helpers.js.map +1 -0
- package/dist/src/runtime/instrumented-page.d.ts +21 -0
- package/dist/src/runtime/instrumented-page.d.ts.map +1 -0
- package/dist/src/runtime/instrumented-page.js +48 -0
- package/dist/src/runtime/instrumented-page.js.map +1 -0
- package/dist/src/runtime/timeline-collector.d.ts +20 -0
- package/dist/src/runtime/timeline-collector.d.ts.map +1 -0
- package/dist/src/runtime/timeline-collector.js +40 -0
- package/dist/src/runtime/timeline-collector.js.map +1 -0
- package/dist/src/timeline/schema.d.ts +297 -0
- package/dist/src/timeline/schema.d.ts.map +1 -0
- package/dist/src/timeline/schema.js +72 -0
- package/dist/src/timeline/schema.js.map +1 -0
- package/dist/src/timeline/types.d.ts +67 -0
- package/dist/src/timeline/types.d.ts.map +1 -0
- package/dist/src/timeline/types.js +2 -0
- package/dist/src/timeline/types.js.map +1 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +2 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/voiceover/narration-timing.d.ts +12 -0
- package/dist/src/voiceover/narration-timing.d.ts.map +1 -0
- package/dist/src/voiceover/narration-timing.js +25 -0
- package/dist/src/voiceover/narration-timing.js.map +1 -0
- package/dist/src/voiceover/piper-engine.d.ts +6 -0
- package/dist/src/voiceover/piper-engine.d.ts.map +1 -0
- package/dist/src/voiceover/piper-engine.js +48 -0
- package/dist/src/voiceover/piper-engine.js.map +1 -0
- package/dist/src/voiceover/voice-models.d.ts +18 -0
- package/dist/src/voiceover/voice-models.d.ts.map +1 -0
- package/dist/src/voiceover/voice-models.js +123 -0
- package/dist/src/voiceover/voice-models.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Guillaume Dupuy
|
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
viewBox="0 0 28 28" enable-background="new 0 0 28 28" xml:space="preserve">
|
|
6
|
+
<polygon fill="#FFFFFF" points="8.2,20.9 8.2,4.9 19.8,16.5 13,16.5 12.6,16.6 "/>
|
|
7
|
+
<polygon fill="#FFFFFF" points="17.3,21.6 13.7,23.1 9,12 12.7,10.5 "/>
|
|
8
|
+
<rect x="12.5" y="13.6" transform="matrix(0.9221 -0.3871 0.3871 0.9221 -5.7605 6.5909)" width="2" height="8"/>
|
|
9
|
+
<polygon points="9.2,7.3 9.2,18.5 12.2,15.6 12.6,15.5 17.4,15.5 "/>
|
|
10
|
+
</svg>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
5
|
+
viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
|
|
6
|
+
<g>
|
|
7
|
+
<defs>
|
|
8
|
+
<rect id="SVGID_1_" width="32" height="32"/>
|
|
9
|
+
</defs>
|
|
10
|
+
<clipPath id="SVGID_2_">
|
|
11
|
+
<use xlink:href="#SVGID_1_" overflow="visible"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
<path clip-path="url(#SVGID_2_)" fill="#FFFFFF" d="M11.3,20.4c-0.3-0.4-0.6-1.1-1.2-2c-0.3-0.5-1.2-1.5-1.5-1.9
|
|
14
|
+
c-0.2-0.4-0.2-0.6-0.1-1c0.1-0.6,0.7-1.1,1.4-1.1c0.5,0,1,0.4,1.4,0.7c0.2,0.2,0.5,0.6,0.7,0.8c0.2,0.2,0.2,0.3,0.4,0.5
|
|
15
|
+
c0.2,0.3,0.3,0.5,0.2,0.1c-0.1-0.5-0.2-1.3-0.4-2.1c-0.1-0.6-0.2-0.7-0.3-1.1c-0.1-0.5-0.2-0.8-0.3-1.3c-0.1-0.3-0.2-1.1-0.3-1.5
|
|
16
|
+
c-0.1-0.5-0.1-1.4,0.3-1.8c0.3-0.3,0.9-0.4,1.3-0.2c0.5,0.3,0.8,1,0.9,1.3c0.2,0.5,0.4,1.2,0.5,2c0.2,1,0.5,2.5,0.5,2.8
|
|
17
|
+
c0-0.4-0.1-1.1,0-1.5c0.1-0.3,0.3-0.7,0.7-0.8c0.3-0.1,0.6-0.1,0.9-0.1c0.3,0.1,0.6,0.3,0.8,0.5c0.4,0.6,0.4,1.9,0.4,1.8
|
|
18
|
+
c0.1-0.4,0.1-1.2,0.3-1.6c0.1-0.2,0.5-0.4,0.7-0.5c0.3-0.1,0.7-0.1,1,0c0.2,0,0.6,0.3,0.7,0.5c0.2,0.3,0.3,1.3,0.4,1.7
|
|
19
|
+
c0,0.1,0.1-0.4,0.3-0.7c0.4-0.6,1.8-0.8,1.9,0.6c0,0.7,0,0.6,0,1.1c0,0.5,0,0.8,0,1.2c0,0.4-0.1,1.3-0.2,1.7
|
|
20
|
+
c-0.1,0.3-0.4,1-0.7,1.4c0,0-1.1,1.2-1.2,1.8c-0.1,0.6-0.1,0.6-0.1,1c0,0.4,0.1,0.9,0.1,0.9s-0.8,0.1-1.2,0c-0.4-0.1-0.9-0.8-1-1.1
|
|
21
|
+
c-0.2-0.3-0.5-0.3-0.7,0c-0.2,0.4-0.7,1.1-1.1,1.1c-0.7,0.1-2.1,0-3.1,0c0,0,0.2-1-0.2-1.4c-0.3-0.3-0.8-0.8-1.1-1.1L11.3,20.4z"/>
|
|
22
|
+
|
|
23
|
+
<path clip-path="url(#SVGID_2_)" fill="none" stroke="#000000" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" d="
|
|
24
|
+
M11.3,20.4c-0.3-0.4-0.6-1.1-1.2-2c-0.3-0.5-1.2-1.5-1.5-1.9c-0.2-0.4-0.2-0.6-0.1-1c0.1-0.6,0.7-1.1,1.4-1.1c0.5,0,1,0.4,1.4,0.7
|
|
25
|
+
c0.2,0.2,0.5,0.6,0.7,0.8c0.2,0.2,0.2,0.3,0.4,0.5c0.2,0.3,0.3,0.5,0.2,0.1c-0.1-0.5-0.2-1.3-0.4-2.1c-0.1-0.6-0.2-0.7-0.3-1.1
|
|
26
|
+
c-0.1-0.5-0.2-0.8-0.3-1.3c-0.1-0.3-0.2-1.1-0.3-1.5c-0.1-0.5-0.1-1.4,0.3-1.8c0.3-0.3,0.9-0.4,1.3-0.2c0.5,0.3,0.8,1,0.9,1.3
|
|
27
|
+
c0.2,0.5,0.4,1.2,0.5,2c0.2,1,0.5,2.5,0.5,2.8c0-0.4-0.1-1.1,0-1.5c0.1-0.3,0.3-0.7,0.7-0.8c0.3-0.1,0.6-0.1,0.9-0.1
|
|
28
|
+
c0.3,0.1,0.6,0.3,0.8,0.5c0.4,0.6,0.4,1.9,0.4,1.8c0.1-0.4,0.1-1.2,0.3-1.6c0.1-0.2,0.5-0.4,0.7-0.5c0.3-0.1,0.7-0.1,1,0
|
|
29
|
+
c0.2,0,0.6,0.3,0.7,0.5c0.2,0.3,0.3,1.3,0.4,1.7c0,0.1,0.1-0.4,0.3-0.7c0.4-0.6,1.8-0.8,1.9,0.6c0,0.7,0,0.6,0,1.1
|
|
30
|
+
c0,0.5,0,0.8,0,1.2c0,0.4-0.1,1.3-0.2,1.7c-0.1,0.3-0.4,1-0.7,1.4c0,0-1.1,1.2-1.2,1.8c-0.1,0.6-0.1,0.6-0.1,1
|
|
31
|
+
c0,0.4,0.1,0.9,0.1,0.9s-0.8,0.1-1.2,0c-0.4-0.1-0.9-0.8-1-1.1c-0.2-0.3-0.5-0.3-0.7,0c-0.2,0.4-0.7,1.1-1.1,1.1
|
|
32
|
+
c-0.7,0.1-2.1,0-3.1,0c0,0,0.2-1-0.2-1.4c-0.3-0.3-0.8-0.8-1.1-1.1L11.3,20.4z"/>
|
|
33
|
+
|
|
34
|
+
<line clip-path="url(#SVGID_2_)" fill="none" stroke="#000000" stroke-width="0.75" stroke-linecap="round" x1="19.6" y1="20.7" x2="19.6" y2="17.3"/>
|
|
35
|
+
|
|
36
|
+
<line clip-path="url(#SVGID_2_)" fill="none" stroke="#000000" stroke-width="0.75" stroke-linecap="round" x1="17.6" y1="20.7" x2="17.5" y2="17.3"/>
|
|
37
|
+
|
|
38
|
+
<line clip-path="url(#SVGID_2_)" fill="none" stroke="#000000" stroke-width="0.75" stroke-linecap="round" x1="15.6" y1="17.3" x2="15.6" y2="20.7"/>
|
|
39
|
+
</g>
|
|
40
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenwright.d.ts","sourceRoot":"","sources":["../../bin/screenwright.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { VERSION } from '../src/version.js';
|
|
4
|
+
import { initCommand } from '../src/commands/init.js';
|
|
5
|
+
import { generateCommand } from '../src/commands/generate.js';
|
|
6
|
+
import { composeCommand } from '../src/commands/compose.js';
|
|
7
|
+
import { previewCommand } from '../src/commands/preview.js';
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name('screenwright')
|
|
11
|
+
.description('Turn Playwright E2E tests into polished product demo videos')
|
|
12
|
+
.version(VERSION);
|
|
13
|
+
program.addCommand(initCommand);
|
|
14
|
+
program.addCommand(generateCommand);
|
|
15
|
+
program.addCommand(composeCommand);
|
|
16
|
+
program.addCommand(previewCommand);
|
|
17
|
+
program.parse();
|
|
18
|
+
//# sourceMappingURL=screenwright.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenwright.js","sourceRoot":"","sources":["../../bin/screenwright.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,cAAc,CAAC;KACpB,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../../src/commands/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,eAAO,MAAM,cAAc,SA+IvB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolve, basename } from 'node:path';
|
|
3
|
+
import { access, mkdir, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { runScenario } from '../runtime/instrumented-page.js';
|
|
8
|
+
import { generateNarration } from '../voiceover/narration-timing.js';
|
|
9
|
+
import { ensureDependencies } from '../voiceover/voice-models.js';
|
|
10
|
+
import { renderDemoVideo } from '../composition/render.js';
|
|
11
|
+
export const composeCommand = new Command('compose')
|
|
12
|
+
.description('Record and compose final demo video')
|
|
13
|
+
.argument('<scenario>', 'Path to demo scenario file')
|
|
14
|
+
.option('--out <path>', 'Output path for final MP4')
|
|
15
|
+
.option('--resolution <res>', 'Video resolution', '1280x720')
|
|
16
|
+
.option('--no-voiceover', 'Disable voiceover')
|
|
17
|
+
.option('--no-cursor', 'Disable cursor overlay')
|
|
18
|
+
.option('--keep-temp', 'Keep temporary files')
|
|
19
|
+
.action(async (scenario, opts) => {
|
|
20
|
+
const scenarioPath = resolve(scenario);
|
|
21
|
+
const [width, height] = opts.resolution.split('x').map(Number);
|
|
22
|
+
if (!width || !height) {
|
|
23
|
+
console.error(chalk.red('Invalid resolution format. Use WIDTHxHEIGHT (e.g., 1280x720)'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
// Verify scenario file exists
|
|
27
|
+
try {
|
|
28
|
+
await access(scenarioPath);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
console.error(chalk.red(`Scenario file not found: ${scenarioPath}`));
|
|
32
|
+
console.error(chalk.dim('Run "screenwright generate --test <path>" to create one.'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const outputDir = resolve(opts.out ? resolve(opts.out, '..') : './output');
|
|
36
|
+
const outputPath = opts.out
|
|
37
|
+
? resolve(opts.out)
|
|
38
|
+
: resolve(outputDir, `${basename(scenarioPath, '.ts')}.mp4`);
|
|
39
|
+
await mkdir(outputDir, { recursive: true });
|
|
40
|
+
// 1. Load scenario module
|
|
41
|
+
let spinner = ora('Loading scenario').start();
|
|
42
|
+
let scenarioFn;
|
|
43
|
+
try {
|
|
44
|
+
const mod = await import(pathToFileURL(scenarioPath).href);
|
|
45
|
+
scenarioFn = mod.default;
|
|
46
|
+
if (typeof scenarioFn !== 'function') {
|
|
47
|
+
spinner.fail('Invalid scenario file');
|
|
48
|
+
console.error(chalk.red('Scenario must export a default async function.'));
|
|
49
|
+
console.error(chalk.dim('Example: export default async function scenario(sw) { ... }'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
spinner.succeed('Scenario loaded');
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
spinner.fail('Failed to load scenario');
|
|
56
|
+
console.error(chalk.red(err.message));
|
|
57
|
+
if (err.message.includes('SyntaxError') || err.message.includes('Cannot find')) {
|
|
58
|
+
console.error(chalk.dim('Make sure the scenario is valid TypeScript and has been compiled.'));
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// 2. Run scenario in Playwright
|
|
63
|
+
spinner = ora('Recording scenario in Playwright').start();
|
|
64
|
+
let timeline, tempDir;
|
|
65
|
+
try {
|
|
66
|
+
const result = await runScenario(scenarioFn, {
|
|
67
|
+
scenarioFile: scenarioPath,
|
|
68
|
+
testFile: scenarioPath,
|
|
69
|
+
viewport: { width, height },
|
|
70
|
+
});
|
|
71
|
+
timeline = result.timeline;
|
|
72
|
+
tempDir = result.tempDir;
|
|
73
|
+
spinner.succeed(`Recorded ${timeline.events.length} events`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
spinner.fail('Recording failed');
|
|
77
|
+
if (err.message.includes('Executable doesn\'t exist') || err.message.includes('browserType.launch')) {
|
|
78
|
+
console.error(chalk.red('Playwright browsers not installed.'));
|
|
79
|
+
console.error(chalk.dim('Run: npx playwright install chromium'));
|
|
80
|
+
}
|
|
81
|
+
else if (err.message.includes('net::ERR_CONNECTION_REFUSED')) {
|
|
82
|
+
console.error(chalk.red('Could not connect to the app.'));
|
|
83
|
+
console.error(chalk.dim('Make sure your dev server is running.'));
|
|
84
|
+
}
|
|
85
|
+
else if (err.message.includes('Timeout') || err.message.includes('waiting for')) {
|
|
86
|
+
console.error(chalk.red('Timed out waiting for an element.'));
|
|
87
|
+
console.error(chalk.dim('Check that selectors in the scenario match your app.'));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.error(chalk.red(err.message));
|
|
91
|
+
}
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
// 3. Generate voiceover (if enabled)
|
|
95
|
+
let finalTimeline = timeline;
|
|
96
|
+
if (opts.voiceover !== false) {
|
|
97
|
+
const narrationCount = timeline.events.filter(e => e.type === 'narration').length;
|
|
98
|
+
if (narrationCount > 0) {
|
|
99
|
+
spinner = ora(`Generating voiceover (${narrationCount} segments)`).start();
|
|
100
|
+
try {
|
|
101
|
+
const { modelPath } = await ensureDependencies();
|
|
102
|
+
finalTimeline = await generateNarration(timeline, {
|
|
103
|
+
tempDir,
|
|
104
|
+
modelPath,
|
|
105
|
+
});
|
|
106
|
+
spinner.succeed(`Generated ${narrationCount} voiceover segments`);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
spinner.warn('Voiceover generation failed — continuing without audio');
|
|
110
|
+
console.error(chalk.dim(err.message));
|
|
111
|
+
console.error(chalk.dim('Tip: use --no-voiceover to skip, or re-run "screenwright init".'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 4. Render final video via Remotion
|
|
116
|
+
spinner = ora('Composing final video').start();
|
|
117
|
+
try {
|
|
118
|
+
await renderDemoVideo({
|
|
119
|
+
timeline: finalTimeline,
|
|
120
|
+
outputPath,
|
|
121
|
+
});
|
|
122
|
+
spinner.succeed('Video composed');
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
spinner.fail('Composition failed');
|
|
126
|
+
if (err.message.includes('memory') || err.message.includes('OOM')) {
|
|
127
|
+
console.error(chalk.red('Out of memory during rendering.'));
|
|
128
|
+
console.error(chalk.dim('Try a lower resolution: --resolution 1280x720'));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.error(chalk.red(err.message));
|
|
132
|
+
}
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
// 5. Cleanup
|
|
136
|
+
if (!opts.keepTemp) {
|
|
137
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.log(chalk.dim(`Temp files kept at: ${tempDir}`));
|
|
141
|
+
}
|
|
142
|
+
// 6. Report
|
|
143
|
+
const fileStats = await stat(outputPath);
|
|
144
|
+
const sizeMB = (fileStats.size / (1024 * 1024)).toFixed(1);
|
|
145
|
+
const durationSec = (finalTimeline.metadata.videoDurationMs / 1000).toFixed(0);
|
|
146
|
+
const mins = Math.floor(Number(durationSec) / 60);
|
|
147
|
+
const secs = Number(durationSec) % 60;
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log(chalk.green(` Demo video saved to: ${outputPath}`));
|
|
150
|
+
console.log(chalk.dim(` Duration: ${mins}:${String(secs).padStart(2, '0')}`));
|
|
151
|
+
console.log(chalk.dim(` Size: ${sizeMB} MB`));
|
|
152
|
+
console.log(chalk.dim(` Events: ${finalTimeline.events.length}`));
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../src/commands/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,eAAe,SAyCxB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolve, basename } from 'node:path';
|
|
3
|
+
import { readFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { prepareGeneration, validateScenarioCode } from '../generator/scenario-generator.js';
|
|
7
|
+
export const generateCommand = new Command('generate')
|
|
8
|
+
.description('Generate demo scenario from a Playwright test')
|
|
9
|
+
.option('--test <path>', 'Path to Playwright test file')
|
|
10
|
+
.option('--validate <path>', 'Validate an existing scenario file')
|
|
11
|
+
.option('--out <path>', 'Output path for generated scenario')
|
|
12
|
+
.option('--narration-style <style>', 'Narration style: brief or detailed', 'detailed')
|
|
13
|
+
.option('--app-description <desc>', 'Brief description of the app for context')
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
if (opts.validate) {
|
|
16
|
+
await runValidation(resolve(opts.validate));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!opts.test) {
|
|
20
|
+
console.error(chalk.red('Error: either --test or --validate is required'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const testPath = resolve(opts.test);
|
|
24
|
+
const outDir = resolve(opts.out ? resolve(opts.out, '..') : './demos');
|
|
25
|
+
const outPath = opts.out
|
|
26
|
+
? resolve(opts.out)
|
|
27
|
+
: resolve(outDir, `${basename(testPath, '.spec.ts')}-demo.ts`);
|
|
28
|
+
await mkdir(outDir, { recursive: true });
|
|
29
|
+
const spinner = ora('Reading test file...').start();
|
|
30
|
+
const { systemPrompt, userPrompt } = await prepareGeneration({
|
|
31
|
+
testPath,
|
|
32
|
+
narrationStyle: opts.narrationStyle,
|
|
33
|
+
appDescription: opts.appDescription,
|
|
34
|
+
});
|
|
35
|
+
spinner.succeed('Test file loaded');
|
|
36
|
+
console.log('\n=== System Prompt ===');
|
|
37
|
+
console.log(systemPrompt);
|
|
38
|
+
console.log('\n=== User Prompt ===');
|
|
39
|
+
console.log(userPrompt);
|
|
40
|
+
console.log(`\n${chalk.cyan('Output path:')} ${outPath}`);
|
|
41
|
+
console.log('Pipe the above prompts to an LLM, then save the response to the output path.');
|
|
42
|
+
console.log('Or use the /screenwright skill in Claude Code for automatic generation.');
|
|
43
|
+
});
|
|
44
|
+
async function runValidation(scenarioPath) {
|
|
45
|
+
const spinner = ora('Reading scenario file...').start();
|
|
46
|
+
let code;
|
|
47
|
+
try {
|
|
48
|
+
code = await readFile(scenarioPath, 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
spinner.fail(`Cannot read ${scenarioPath}: ${err.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
spinner.succeed('Scenario file loaded');
|
|
55
|
+
const result = validateScenarioCode(code);
|
|
56
|
+
for (const e of result.errors) {
|
|
57
|
+
console.log(chalk.red(` ERROR [${e.code}]: ${e.message}`));
|
|
58
|
+
}
|
|
59
|
+
for (const w of result.warnings) {
|
|
60
|
+
console.log(chalk.yellow(` WARN [${w.code}]: ${w.message}`));
|
|
61
|
+
}
|
|
62
|
+
if (result.valid) {
|
|
63
|
+
console.log(chalk.green(' Scenario is valid.'));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/commands/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,WAAW,SAyCpB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { writeFile, access } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { defaultConfig, serializeConfig } from '../config/defaults.js';
|
|
7
|
+
import { ensureDependencies } from '../voiceover/voice-models.js';
|
|
8
|
+
export const initCommand = new Command('init')
|
|
9
|
+
.description('Bootstrap config and download voice model')
|
|
10
|
+
.option('--voice <model>', 'Voice model to use', 'en_US-amy-medium')
|
|
11
|
+
.option('--skip-voice-download', 'Skip downloading the voice model')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const configPath = resolve(process.cwd(), 'screenwright.config.ts');
|
|
14
|
+
// Config file
|
|
15
|
+
let configExists = false;
|
|
16
|
+
try {
|
|
17
|
+
await access(configPath);
|
|
18
|
+
configExists = true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// doesn't exist
|
|
22
|
+
}
|
|
23
|
+
if (configExists) {
|
|
24
|
+
console.log(chalk.dim('screenwright.config.ts already exists, skipping.'));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const config = { ...defaultConfig, voice: opts.voice };
|
|
28
|
+
await writeFile(configPath, serializeConfig(config), 'utf-8');
|
|
29
|
+
console.log(chalk.green('Created screenwright.config.ts'));
|
|
30
|
+
}
|
|
31
|
+
// Voice model
|
|
32
|
+
if (!opts.skipVoiceDownload) {
|
|
33
|
+
const spinner = ora('Downloading Piper TTS and voice model').start();
|
|
34
|
+
try {
|
|
35
|
+
await ensureDependencies(opts.voice);
|
|
36
|
+
spinner.succeed('Piper TTS and voice model ready');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
spinner.warn('Could not download voice model');
|
|
40
|
+
console.error(chalk.dim(err.message));
|
|
41
|
+
console.error(chalk.dim('Voiceover will be unavailable. Re-run "screenwright init" to retry.'));
|
|
42
|
+
console.error(chalk.dim('Use --no-voiceover with compose to skip voiceover.'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(chalk.green('Screenwright initialized.'));
|
|
47
|
+
console.log(chalk.dim('Next: screenwright generate --test <path>'));
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,EAAE,kBAAkB,CAAC;KACnE,MAAM,CAAC,uBAAuB,EAAE,kCAAkC,CAAC;KACnE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,wBAAwB,CAAC,CAAC;IAEpE,cAAc;IACd,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QACzB,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;IAED,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,EAAE,GAAG,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACvD,MAAM,SAAS,CAAC,UAAU,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,cAAc;IACd,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,GAAG,CAAC,uCAAuC,CAAC,CAAC,KAAK,EAAE,CAAC;QACrE,IAAI,CAAC;YACH,MAAM,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrC,OAAO,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAC/C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC,CAAC;YAChG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/commands/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,cAAc,SAwDvB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolve, basename } from 'node:path';
|
|
3
|
+
import { access, mkdir, copyFile } from 'node:fs/promises';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { runScenario } from '../runtime/instrumented-page.js';
|
|
8
|
+
export const previewCommand = new Command('preview')
|
|
9
|
+
.description('Quick preview without cursor overlay or voiceover')
|
|
10
|
+
.argument('<scenario>', 'Path to demo scenario file')
|
|
11
|
+
.option('--out <path>', 'Output path for preview video')
|
|
12
|
+
.action(async (scenario, opts) => {
|
|
13
|
+
const scenarioPath = resolve(scenario);
|
|
14
|
+
try {
|
|
15
|
+
await access(scenarioPath);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
console.error(chalk.red(`Scenario file not found: ${scenarioPath}`));
|
|
19
|
+
console.error(chalk.dim('Run "screenwright generate --test <path>" to create one.'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const outputDir = resolve(opts.out ? resolve(opts.out, '..') : './output');
|
|
23
|
+
const outputPath = opts.out
|
|
24
|
+
? resolve(opts.out)
|
|
25
|
+
: resolve(outputDir, `${basename(scenarioPath, '.ts')}-preview.webm`);
|
|
26
|
+
await mkdir(outputDir, { recursive: true });
|
|
27
|
+
// 1. Load scenario module
|
|
28
|
+
let spinner = ora('Loading scenario').start();
|
|
29
|
+
let scenarioFn;
|
|
30
|
+
try {
|
|
31
|
+
const mod = await import(pathToFileURL(scenarioPath).href);
|
|
32
|
+
scenarioFn = mod.default;
|
|
33
|
+
if (typeof scenarioFn !== 'function') {
|
|
34
|
+
spinner.fail('Invalid scenario file');
|
|
35
|
+
console.error(chalk.red('Scenario must export a default async function.'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
spinner.succeed('Scenario loaded');
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
spinner.fail('Failed to load scenario');
|
|
42
|
+
console.error(chalk.red(err.message));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// 2. Run scenario
|
|
46
|
+
spinner = ora('Recording preview').start();
|
|
47
|
+
try {
|
|
48
|
+
const { videoFile, timeline } = await runScenario(scenarioFn, {
|
|
49
|
+
scenarioFile: scenarioPath,
|
|
50
|
+
testFile: scenarioPath,
|
|
51
|
+
});
|
|
52
|
+
await copyFile(videoFile, outputPath);
|
|
53
|
+
spinner.succeed(`Preview saved to: ${outputPath}`);
|
|
54
|
+
console.log(chalk.dim(` ${timeline.events.length} events recorded`));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
spinner.fail('Recording failed');
|
|
58
|
+
console.error(chalk.red(err.message));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/commands/preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ActionEvent } from '../timeline/types.js';
|
|
3
|
+
import { type CursorEventWithWaypoints } from './cursor-path.js';
|
|
4
|
+
interface Props {
|
|
5
|
+
cursorEvents: CursorEventWithWaypoints[];
|
|
6
|
+
clickEvents: ActionEvent[];
|
|
7
|
+
fps: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const CursorOverlay: React.FC<Props>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=CursorOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CursorOverlay.d.ts","sourceRoot":"","sources":["../../../src/composition/CursorOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAGpF,UAAU,KAAK;IACb,YAAY,EAAE,wBAAwB,EAAE,CAAC;IACzC,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAqDzC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCurrentFrame } from 'remotion';
|
|
3
|
+
import { getCursorPosition } from './cursor-path.js';
|
|
4
|
+
import { interpolate } from 'remotion';
|
|
5
|
+
export const CursorOverlay = ({ cursorEvents, clickEvents, fps }) => {
|
|
6
|
+
const frame = useCurrentFrame();
|
|
7
|
+
const timeMs = (frame / fps) * 1000;
|
|
8
|
+
const { x, y } = getCursorPosition(cursorEvents, timeMs);
|
|
9
|
+
// Click ripple effect
|
|
10
|
+
const activeClick = clickEvents.find(e => timeMs >= e.timestampMs && timeMs <= e.timestampMs + 300);
|
|
11
|
+
const rippleProgress = activeClick ? timeMs - activeClick.timestampMs : 0;
|
|
12
|
+
return (_jsxs("div", { style: { position: 'absolute', inset: 0, pointerEvents: 'none' }, children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", style: {
|
|
13
|
+
position: 'absolute',
|
|
14
|
+
left: x - 4,
|
|
15
|
+
top: y - 2,
|
|
16
|
+
filter: 'drop-shadow(1px 1px 2px rgba(0,0,0,0.3))',
|
|
17
|
+
}, children: _jsx("path", { d: "M5.5 3.21V20.8a.5.5 0 0 0 .85.36l4.86-4.86h6.18a.5.5 0 0 0 .36-.86L5.86 3.21a.5.5 0 0 0-.36.0z", fill: "white", stroke: "black", strokeWidth: "1.5" }) }), activeClick && (_jsx("div", { style: {
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
left: x - 20,
|
|
20
|
+
top: y - 20,
|
|
21
|
+
width: 40,
|
|
22
|
+
height: 40,
|
|
23
|
+
borderRadius: '50%',
|
|
24
|
+
border: '2px solid rgba(59, 130, 246, 0.6)',
|
|
25
|
+
opacity: interpolate(rippleProgress, [0, 300], [1, 0]),
|
|
26
|
+
transform: `scale(${interpolate(rippleProgress, [0, 300], [0.5, 1.5])})`,
|
|
27
|
+
} }))] }));
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=CursorOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CursorOverlay.js","sourceRoot":"","sources":["../../../src/composition/CursorOverlay.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAiC,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQvC,MAAM,CAAC,MAAM,aAAa,GAAoB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE;IACnF,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;IAEpC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,iBAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAEzD,sBAAsB;IACtB,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAClC,CAAC,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,CAAC,WAAW,GAAG,GAAG,CAC9D,CAAC;IAEF,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1E,OAAO,CACL,eAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,aAEnE,cACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,KAAK,EAAE;oBACL,QAAQ,EAAE,UAAU;oBACpB,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,GAAG,EAAE,CAAC,GAAG,CAAC;oBACV,MAAM,EAAE,0CAA0C;iBACnD,YAED,eACE,CAAC,EAAC,gGAAgG,EAClG,IAAI,EAAC,OAAO,EACZ,MAAM,EAAC,OAAO,EACd,WAAW,EAAC,KAAK,GACjB,GACE,EAGL,WAAW,IAAI,CACd,cACE,KAAK,EAAE;oBACL,QAAQ,EAAE,UAAU;oBACpB,IAAI,EAAE,CAAC,GAAG,EAAE;oBACZ,GAAG,EAAE,CAAC,GAAG,EAAE;oBACX,KAAK,EAAE,EAAE;oBACT,MAAM,EAAE,EAAE;oBACV,YAAY,EAAE,KAAK;oBACnB,MAAM,EAAE,mCAAmC;oBAC3C,OAAO,EAAE,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACtD,SAAS,EAAE,SAAS,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG;iBACzE,GACD,CACH,IACG,CACP,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DemoVideo.d.ts","sourceRoot":"","sources":["../../../src/composition/DemoVideo.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK/D,UAAU,KAAK;IACb,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAkCrC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { OffthreadVideo } from 'remotion';
|
|
3
|
+
import { CursorOverlay } from './CursorOverlay.js';
|
|
4
|
+
import { NarrationTrack } from './NarrationTrack.js';
|
|
5
|
+
import { precomputeCursorPaths } from './cursor-path.js';
|
|
6
|
+
export const DemoVideo = ({ timeline }) => {
|
|
7
|
+
const fps = 30;
|
|
8
|
+
const cursorEvents = precomputeCursorPaths(timeline.events.filter((e) => e.type === 'cursor_target'));
|
|
9
|
+
const clickEvents = timeline.events.filter((e) => e.type === 'action' && e.action === 'click');
|
|
10
|
+
const narrations = timeline.events.filter((e) => e.type === 'narration');
|
|
11
|
+
return (_jsxs("div", { style: {
|
|
12
|
+
position: 'relative',
|
|
13
|
+
width: timeline.metadata.viewport.width,
|
|
14
|
+
height: timeline.metadata.viewport.height,
|
|
15
|
+
overflow: 'hidden',
|
|
16
|
+
}, children: [_jsx(OffthreadVideo, { src: timeline.metadata.videoFile }), _jsx(CursorOverlay, { cursorEvents: cursorEvents, clickEvents: clickEvents, fps: fps }), _jsx(NarrationTrack, { narrations: narrations, fps: fps })] }));
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=DemoVideo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DemoVideo.js","sourceRoot":"","sources":["../../../src/composition/DemoVideo.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAMzD,MAAM,CAAC,MAAM,SAAS,GAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;IACzD,MAAM,GAAG,GAAG,EAAE,CAAC;IAEf,MAAM,YAAY,GAAG,qBAAqB,CACxC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAA0B,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAClF,CAAC;IAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CACxC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,CACrE,CAAC;IAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CACvC,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CACnD,CAAC;IAEF,OAAO,CACL,eACE,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK;YACvC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM;YACzC,QAAQ,EAAE,QAAQ;SACnB,aAGD,KAAC,cAAc,IAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,SAAS,GAAI,EAGpD,KAAC,aAAa,IAAC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,GAAI,EAGjF,KAAC,cAAc,IAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,GAAI,IAChD,CACP,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { NarrationEvent } from '../timeline/types.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
narrations: NarrationEvent[];
|
|
5
|
+
fps: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const NarrationTrack: React.FC<Props>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=NarrationTrack.d.ts.map
|