@spark-apps/quickpeek 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +61 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +180 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/crawl.d.ts +18 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +175 -0
- package/dist/crawl.js.map +1 -0
- package/dist/cursor.d.ts +6 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +6 -0
- package/dist/cursor.js.map +1 -0
- package/dist/ffmpeg.d.ts +50 -0
- package/dist/ffmpeg.d.ts.map +1 -0
- package/dist/ffmpeg.js +224 -0
- package/dist/ffmpeg.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +91 -0
- package/dist/prompts.js.map +1 -0
- package/dist/styles.d.ts +16 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +66 -0
- package/dist/styles.js.map +1 -0
- package/dist/tts.d.ts +64 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +236 -0
- package/dist/tts.js.map +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +106 -0
- package/dist/utils.js.map +1 -0
- package/dist/voices.d.ts +2 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +73 -0
- package/dist/voices.js.map +1 -0
- package/dist/watermark.d.ts +27 -0
- package/dist/watermark.d.ts.map +1 -0
- package/dist/watermark.js +57 -0
- package/dist/watermark.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../src/ffmpeg.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkClE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3E;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAsHhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ChE"}
|
package/dist/ffmpeg.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { buildWatermarkFilters } from './watermark.js';
|
|
3
|
+
/**
|
|
4
|
+
* Run ffprobe to get media duration in milliseconds
|
|
5
|
+
*/
|
|
6
|
+
export function getMediaDuration(filePath) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const args = [
|
|
9
|
+
'-v',
|
|
10
|
+
'error',
|
|
11
|
+
'-show_entries',
|
|
12
|
+
'format=duration',
|
|
13
|
+
'-of',
|
|
14
|
+
'default=noprint_wrappers=1:nokey=1',
|
|
15
|
+
filePath,
|
|
16
|
+
];
|
|
17
|
+
const proc = spawn('ffprobe', args);
|
|
18
|
+
let stdout = '';
|
|
19
|
+
let stderr = '';
|
|
20
|
+
proc.stdout.on('data', (data) => {
|
|
21
|
+
stdout += data;
|
|
22
|
+
});
|
|
23
|
+
proc.stderr.on('data', (data) => {
|
|
24
|
+
stderr += data;
|
|
25
|
+
});
|
|
26
|
+
proc.on('close', (code) => {
|
|
27
|
+
if (code !== 0) {
|
|
28
|
+
reject(new Error(`ffprobe failed: ${stderr}`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const duration = parseFloat(stdout.trim());
|
|
32
|
+
resolve(Math.ceil(duration * 1000));
|
|
33
|
+
});
|
|
34
|
+
proc.on('error', reject);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Concatenate audio files using ffmpeg concat demuxer
|
|
39
|
+
*/
|
|
40
|
+
export function concatMedia(listPath, output) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const args = ['-y', '-f', 'concat', '-safe', '0', '-i', listPath, '-c', 'copy', output];
|
|
43
|
+
const proc = spawn('ffmpeg', args);
|
|
44
|
+
let stderr = '';
|
|
45
|
+
proc.stderr.on('data', (data) => {
|
|
46
|
+
stderr += data;
|
|
47
|
+
});
|
|
48
|
+
proc.on('close', (code) => {
|
|
49
|
+
if (code !== 0) {
|
|
50
|
+
reject(new Error(`ffmpeg concat failed: ${stderr}`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
55
|
+
proc.on('error', reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compose final video with subtitles, voiceover, and music
|
|
60
|
+
*/
|
|
61
|
+
export function composeVideo(opts) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const { videoPath, assPath, outputPath, voiceoverPath, musicPath, musicVolume = 0.3, musicFadeIn = 2, musicFadeOut = 2, width, height, fps, preset, quality, fadeIn, fadeOut, videoDuration, audioBitrate = '192k', contrast = 0, trimStart = 0, userTier = 'free', } = opts;
|
|
64
|
+
// Trim white frames from start of Playwright recording
|
|
65
|
+
const args = ['-y'];
|
|
66
|
+
if (trimStart > 0) {
|
|
67
|
+
args.push('-ss', String(trimStart));
|
|
68
|
+
}
|
|
69
|
+
args.push('-i', videoPath);
|
|
70
|
+
// Adjust duration for trimmed content
|
|
71
|
+
const effectiveDuration = videoDuration - trimStart;
|
|
72
|
+
// Add input streams
|
|
73
|
+
if (voiceoverPath)
|
|
74
|
+
args.push('-i', voiceoverPath);
|
|
75
|
+
if (musicPath)
|
|
76
|
+
args.push('-i', musicPath);
|
|
77
|
+
// Build video filter
|
|
78
|
+
const vf = [`scale=${width}:${height}`, `fps=${fps}`];
|
|
79
|
+
// Add contrast filter if enabled (contrast > 0)
|
|
80
|
+
// Map 0-1 range to ffmpeg's eq filter: 0 = no change (1.0), 1 = double contrast (2.0)
|
|
81
|
+
if (contrast > 0) {
|
|
82
|
+
const ffmpegContrast = 1 + contrast;
|
|
83
|
+
vf.push(`eq=contrast=${ffmpegContrast.toFixed(2)}`);
|
|
84
|
+
}
|
|
85
|
+
// Add watermark filters for free tier videos >20 seconds
|
|
86
|
+
const watermarkFilters = buildWatermarkFilters({ tier: userTier, videoDuration: effectiveDuration });
|
|
87
|
+
if (watermarkFilters) {
|
|
88
|
+
vf.push(watermarkFilters);
|
|
89
|
+
}
|
|
90
|
+
vf.push(`ass=${assPath.replace(/\\/g, '/').replace(/:/g, '\\:')}`);
|
|
91
|
+
if (fadeIn > 0)
|
|
92
|
+
vf.push(`fade=t=in:st=0:d=${fadeIn}:c=black`);
|
|
93
|
+
if (fadeOut > 0) {
|
|
94
|
+
const videoFadeOutStart = Math.max(0, effectiveDuration - fadeOut);
|
|
95
|
+
vf.push(`fade=t=out:st=${videoFadeOutStart}:d=${fadeOut}:c=black`);
|
|
96
|
+
}
|
|
97
|
+
args.push('-vf', vf.join(','));
|
|
98
|
+
// Convert 0-1 quality to CRF (1.0 = CRF 18 best, 0.0 = CRF 28 smallest)
|
|
99
|
+
const crf = Math.round(28 - quality * 10);
|
|
100
|
+
args.push('-c:v', 'libx264', '-preset', preset, '-crf', String(crf), '-pix_fmt', 'yuv420p');
|
|
101
|
+
// Handle audio mixing
|
|
102
|
+
const hasVoiceover = !!voiceoverPath;
|
|
103
|
+
const hasMusic = !!musicPath;
|
|
104
|
+
const musicFadeOutStart = Math.max(0, effectiveDuration - musicFadeOut);
|
|
105
|
+
const voiceFadeOutStart = Math.max(0, effectiveDuration - fadeOut);
|
|
106
|
+
if (hasVoiceover && hasMusic) {
|
|
107
|
+
// Pad voiceover to video duration, loop music to video duration, then mix
|
|
108
|
+
const complexFilter = [
|
|
109
|
+
`[1:a]apad=whole_dur=${effectiveDuration},afade=t=in:st=0:d=${fadeIn},afade=t=out:st=${voiceFadeOutStart}:d=${fadeOut}[voice]`,
|
|
110
|
+
`[2:a]aloop=loop=-1:size=2e+09,atrim=0:${effectiveDuration},volume=${musicVolume},afade=t=in:st=0:d=${musicFadeIn},afade=t=out:st=${musicFadeOutStart}:d=${musicFadeOut}[music]`,
|
|
111
|
+
`[voice][music]amix=inputs=2:duration=shortest[aout]`,
|
|
112
|
+
].join(';');
|
|
113
|
+
args.push('-filter_complex', complexFilter, '-map', '0:v', '-map', '[aout]');
|
|
114
|
+
args.push('-c:a', 'aac', '-b:a', audioBitrate);
|
|
115
|
+
}
|
|
116
|
+
else if (hasVoiceover) {
|
|
117
|
+
// Pad voiceover to video duration
|
|
118
|
+
const af = `apad=whole_dur=${effectiveDuration},afade=t=in:st=0:d=${fadeIn},afade=t=out:st=${voiceFadeOutStart}:d=${fadeOut}`;
|
|
119
|
+
args.push('-map', '0:v', '-map', '1:a', '-af', af);
|
|
120
|
+
args.push('-c:a', 'aac', '-b:a', audioBitrate);
|
|
121
|
+
}
|
|
122
|
+
else if (hasMusic) {
|
|
123
|
+
// Loop music to video duration
|
|
124
|
+
const musicFilter = `aloop=loop=-1:size=2e+09,atrim=0:${effectiveDuration},volume=${musicVolume},afade=t=in:st=0:d=${musicFadeIn},afade=t=out:st=${musicFadeOutStart}:d=${musicFadeOut}`;
|
|
125
|
+
args.push('-map', '0:v', '-map', '1:a', '-af', musicFilter);
|
|
126
|
+
args.push('-c:a', 'aac', '-b:a', '192k');
|
|
127
|
+
}
|
|
128
|
+
args.push(outputPath);
|
|
129
|
+
// Debug: log ffmpeg command
|
|
130
|
+
// console.log('ffmpeg', args.join(' '));
|
|
131
|
+
const proc = spawn('ffmpeg', args);
|
|
132
|
+
let stderr = '';
|
|
133
|
+
proc.stderr.on('data', (data) => {
|
|
134
|
+
stderr += data;
|
|
135
|
+
});
|
|
136
|
+
proc.on('close', (code) => {
|
|
137
|
+
if (code !== 0) {
|
|
138
|
+
reject(new Error(`ffmpeg compose failed: ${stderr}`));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
proc.on('error', reject);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate silence audio file of specified duration
|
|
148
|
+
*/
|
|
149
|
+
export function generateSilence(outputPath, durationMs) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const durationSec = durationMs / 1000;
|
|
152
|
+
const args = [
|
|
153
|
+
'-y',
|
|
154
|
+
'-f',
|
|
155
|
+
'lavfi',
|
|
156
|
+
'-i',
|
|
157
|
+
`anullsrc=r=24000:cl=mono`,
|
|
158
|
+
'-t',
|
|
159
|
+
String(durationSec),
|
|
160
|
+
'-c:a',
|
|
161
|
+
'libmp3lame',
|
|
162
|
+
'-b:a',
|
|
163
|
+
'96k',
|
|
164
|
+
outputPath,
|
|
165
|
+
];
|
|
166
|
+
const proc = spawn('ffmpeg', args);
|
|
167
|
+
let stderr = '';
|
|
168
|
+
proc.stderr.on('data', (data) => {
|
|
169
|
+
stderr += data;
|
|
170
|
+
});
|
|
171
|
+
proc.on('close', (code) => {
|
|
172
|
+
if (code !== 0) {
|
|
173
|
+
reject(new Error(`ffmpeg silence generation failed: ${stderr}`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
proc.on('error', reject);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Speed up video by a factor
|
|
183
|
+
*/
|
|
184
|
+
export function speedUpVideo(opts) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
const { inputPath, outputPath, factor, preset } = opts;
|
|
187
|
+
// setpts for video speed
|
|
188
|
+
const videoFilter = `setpts=${(1 / factor).toFixed(3)}*PTS`;
|
|
189
|
+
// atempo only supports 0.5-2.0, so chain multiple for higher speeds
|
|
190
|
+
const audioFilters = [];
|
|
191
|
+
let remaining = factor;
|
|
192
|
+
while (remaining > 1) {
|
|
193
|
+
const tempo = Math.min(remaining, 2.0);
|
|
194
|
+
audioFilters.push(`atempo=${tempo.toFixed(2)}`);
|
|
195
|
+
remaining /= tempo;
|
|
196
|
+
}
|
|
197
|
+
const args = [
|
|
198
|
+
'-y',
|
|
199
|
+
'-i',
|
|
200
|
+
inputPath,
|
|
201
|
+
'-vf',
|
|
202
|
+
videoFilter,
|
|
203
|
+
'-af',
|
|
204
|
+
audioFilters.join(','),
|
|
205
|
+
'-preset',
|
|
206
|
+
preset,
|
|
207
|
+
outputPath,
|
|
208
|
+
];
|
|
209
|
+
const proc = spawn('ffmpeg', args);
|
|
210
|
+
let stderr = '';
|
|
211
|
+
proc.stderr.on('data', (data) => {
|
|
212
|
+
stderr += data;
|
|
213
|
+
});
|
|
214
|
+
proc.on('close', (code) => {
|
|
215
|
+
if (code !== 0) {
|
|
216
|
+
reject(new Error(`ffmpeg speedup failed: ${stderr}`));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
resolve();
|
|
220
|
+
});
|
|
221
|
+
proc.on('error', reject);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../src/ffmpeg.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuickPeek Core - Shared library for demo video generation
|
|
3
|
+
*/
|
|
4
|
+
export * from './api-client.js';
|
|
5
|
+
export * from './config.js';
|
|
6
|
+
export * from './crawl.js';
|
|
7
|
+
export * from './cursor.js';
|
|
8
|
+
export * from './ffmpeg.js';
|
|
9
|
+
export * from './prompts.js';
|
|
10
|
+
export * from './styles.js';
|
|
11
|
+
export * from './tts.js';
|
|
12
|
+
export * from './utils.js';
|
|
13
|
+
export * from './voices.js';
|
|
14
|
+
export * from './watermark.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,iBAAiB,CAAC;AAGhC,cAAc,aAAa,CAAC;AAE5B,cAAc,YAAY,CAAC;AAE3B,cAAc,aAAa,CAAC;AAE5B,cAAc,aAAa,CAAC;AAG5B,cAAc,cAAc,CAAC;AAE7B,cAAc,aAAa,CAAC;AAE5B,cAAc,UAAU,CAAC;AAEzB,cAAc,YAAY,CAAC;AAE3B,cAAc,aAAa,CAAC;AAE5B,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuickPeek Core - Shared library for demo video generation
|
|
3
|
+
*/
|
|
4
|
+
// API client for backend communication
|
|
5
|
+
export * from './api-client.js';
|
|
6
|
+
// Configuration types and defaults
|
|
7
|
+
export * from './config.js';
|
|
8
|
+
// Page crawling (requires Playwright as peer dependency)
|
|
9
|
+
export * from './crawl.js';
|
|
10
|
+
// Cursor asset
|
|
11
|
+
export * from './cursor.js';
|
|
12
|
+
// FFmpeg video/audio processing
|
|
13
|
+
export * from './ffmpeg.js';
|
|
14
|
+
// AI prompt building
|
|
15
|
+
export * from './prompts.js';
|
|
16
|
+
// CSS/ASS styling for subtitles
|
|
17
|
+
export * from './styles.js';
|
|
18
|
+
// Text-to-speech voiceover generation
|
|
19
|
+
export * from './tts.js';
|
|
20
|
+
// Utility functions
|
|
21
|
+
export * from './utils.js';
|
|
22
|
+
// Voice mappings for TTS
|
|
23
|
+
export * from './voices.js';
|
|
24
|
+
// Watermark filter generation
|
|
25
|
+
export * from './watermark.js';
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,cAAc,iBAAiB,CAAC;AAEhC,mCAAmC;AACnC,cAAc,aAAa,CAAC;AAC5B,yDAAyD;AACzD,cAAc,YAAY,CAAC;AAC3B,eAAe;AACf,cAAc,aAAa,CAAC;AAC5B,gCAAgC;AAChC,cAAc,aAAa,CAAC;AAE5B,qBAAqB;AACrB,cAAc,cAAc,CAAC;AAC7B,gCAAgC;AAChC,cAAc,aAAa,CAAC;AAC5B,sCAAsC;AACtC,cAAc,UAAU,CAAC;AACzB,oBAAoB;AACpB,cAAc,YAAY,CAAC;AAC3B,yBAAyB;AACzB,cAAc,aAAa,CAAC;AAC5B,8BAA8B;AAC9B,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI prompts for demo plan generation
|
|
3
|
+
*/
|
|
4
|
+
import { getLanguageName } from './utils.js';
|
|
5
|
+
interface ElementInfo {
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
selector: string;
|
|
9
|
+
}
|
|
10
|
+
export { getLanguageName };
|
|
11
|
+
export declare function buildSystemPrompt(maxSteps: number): string;
|
|
12
|
+
export declare function buildUserPrompt(url: string, title: string, elements: ElementInfo[], langName: string | null, description: string | undefined, maxSteps: number): string;
|
|
13
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuD1D;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,QAAQ,EAAE,MAAM,GACf,MAAM,CA4BR"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI prompts for demo plan generation
|
|
3
|
+
*/
|
|
4
|
+
import { getLanguageName } from './utils.js';
|
|
5
|
+
export { getLanguageName };
|
|
6
|
+
export function buildSystemPrompt(maxSteps) {
|
|
7
|
+
// Tier-based scope guidance
|
|
8
|
+
let scope;
|
|
9
|
+
if (maxSteps <= 5) {
|
|
10
|
+
scope = `SCOPE: Minimal demo (${maxSteps} steps max) - ONE complete user journey only`;
|
|
11
|
+
}
|
|
12
|
+
else if (maxSteps <= 8) {
|
|
13
|
+
scope = `SCOPE: Standard demo (${maxSteps} steps max) - One journey with 1-2 options`;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
scope = `SCOPE: Extended demo (${maxSteps} steps max) - Journey plus key settings`;
|
|
17
|
+
}
|
|
18
|
+
return `You create demo video scripts for web apps.
|
|
19
|
+
|
|
20
|
+
${scope}
|
|
21
|
+
|
|
22
|
+
CORE FLOW (always follow this structure):
|
|
23
|
+
1. wait - Intro (what does this app do?)
|
|
24
|
+
2. Actions - Complete ONE user journey (upload→process→result)
|
|
25
|
+
3. wait - Outro (what was accomplished?)
|
|
26
|
+
|
|
27
|
+
SELECTOR RULES:
|
|
28
|
+
• Visible buttons: use exact text from AVAILABLE ELEMENTS
|
|
29
|
+
• Hidden buttons (appear after actions): use *Keyword - TRY THESE IN ORDER:
|
|
30
|
+
- Processing: *Extract (preferred), *Generate, *Create, *Convert, *Submit
|
|
31
|
+
- Results: *Download (preferred), *Save, *Export
|
|
32
|
+
• ONLY target real buttons/links - never labels, images, sliders, or files
|
|
33
|
+
|
|
34
|
+
SKIP THESE ELEMENTS (never include in demo):
|
|
35
|
+
• Navigation links: Home, About, Contact, Help, Support, FAQ
|
|
36
|
+
• Account links: Login, Sign In, Sign Up, Register, My Account
|
|
37
|
+
• Business links: Pricing, Plans, Subscribe, Buy, Upgrade
|
|
38
|
+
• Footer links: Privacy, Terms, Blog, Careers, Social media
|
|
39
|
+
• Any header/footer navigation - focus ONLY on the main app functionality
|
|
40
|
+
|
|
41
|
+
FORBIDDEN:
|
|
42
|
+
• Repeating actions (one upload, one process, one download)
|
|
43
|
+
• Clicking downloaded files or results
|
|
44
|
+
• Vague targets like "slider", "animation", "options"
|
|
45
|
+
• More steps than maxSteps allows
|
|
46
|
+
|
|
47
|
+
ACTION FORMATS (use CSS selectors, not descriptions):
|
|
48
|
+
• click: { "action": "click", "target": "button text or CSS selector" }
|
|
49
|
+
• type: { "action": "type", "target": "input[name=...] or #id", "value": "text" }
|
|
50
|
+
• upload: { "action": "upload", "target": "input[type=\\"file\\"]", "value": "[file]" }
|
|
51
|
+
• wait: { "action": "wait" } - no target needed
|
|
52
|
+
• scroll:
|
|
53
|
+
- Page: { "action": "scroll", "value": "up|down" }
|
|
54
|
+
- Zoom: { "action": "scroll", "value": "10|-10" } - positive=in, negative=out (or "in|out")
|
|
55
|
+
• drag:
|
|
56
|
+
- Sliders: { "action": "drag", "target": "input[type=\\"range\\"]", "value": "10" }
|
|
57
|
+
- Rotation: { "action": "drag", "value": "around" } - auto-targets canvas
|
|
58
|
+
• select: { "action": "select", "target": "select#id or select[name=...]", "value": "option" }
|
|
59
|
+
• check: { "action": "check", "target": "input[type=\\"checkbox\\"]" }
|
|
60
|
+
|
|
61
|
+
SELECTOR PRIORITY: CSS selectors > exact button text > *Keyword wildcard`;
|
|
62
|
+
}
|
|
63
|
+
export function buildUserPrompt(url, title, elements, langName, description, maxSteps) {
|
|
64
|
+
const lang = langName ? `\nLanguage: Write ALL captions in ${langName}.` : '';
|
|
65
|
+
const purpose = description ? `\nPurpose: ${description}` : '';
|
|
66
|
+
const minSteps = Math.max(3, Math.floor(maxSteps * 0.6));
|
|
67
|
+
return `URL: ${url}
|
|
68
|
+
Title: ${title}${purpose}
|
|
69
|
+
|
|
70
|
+
AVAILABLE ELEMENTS:
|
|
71
|
+
${JSON.stringify(elements, null, 2)}
|
|
72
|
+
|
|
73
|
+
REQUIREMENTS:
|
|
74
|
+
• ${minSteps}-${maxSteps} steps total (start with wait intro, end with wait outro)
|
|
75
|
+
• Complete ONE user journey: input → process → output
|
|
76
|
+
• Use *Keyword for buttons that appear after actions
|
|
77
|
+
• Final action should be download/save/submit - never click results${lang}
|
|
78
|
+
|
|
79
|
+
Return JSON:
|
|
80
|
+
{
|
|
81
|
+
"url": "${url}",
|
|
82
|
+
"title": "Short demo title",
|
|
83
|
+
"description": "What this app does",
|
|
84
|
+
"steps": [
|
|
85
|
+
{ "id": 1, "action": "wait", "caption": "Intro..." },
|
|
86
|
+
{ "id": 2, "action": "click|upload|type", "target": "selector", "caption": "..." },
|
|
87
|
+
{ "id": N, "action": "wait", "caption": "Outro..." }
|
|
88
|
+
]
|
|
89
|
+
}`;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQ7C,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,4BAA4B;IAC5B,IAAI,KAAa,CAAC;IAClB,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,KAAK,GAAG,wBAAwB,QAAQ,8CAA8C,CAAC;IACzF,CAAC;SAAM,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QACzB,KAAK,GAAG,yBAAyB,QAAQ,4CAA4C,CAAC;IACxF,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,yBAAyB,QAAQ,yCAAyC,CAAC;IACrF,CAAC;IAED,OAAO;;EAEP,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAyCkE,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,KAAa,EACb,QAAuB,EACvB,QAAuB,EACvB,WAA+B,EAC/B,QAAgB;IAEhB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,qCAAqC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,cAAc,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC;IAEzD,OAAO,QAAQ,GAAG;SACX,KAAK,GAAG,OAAO;;;EAGtB,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;;;IAG/B,QAAQ,IAAI,QAAQ;;;qEAG6C,IAAI;;;;YAI7D,GAAG;;;;;;;;EAQb,CAAC;AACH,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS and ASS styling for QuickPeek
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* CSS for the custom cursor overlay
|
|
6
|
+
*/
|
|
7
|
+
export declare const CURSOR_CSS = "\n #qp-cursor {\n position: fixed;\n width: 48px;\n height: 56px;\n background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAEsBAMAAAB01OGNAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAtUExURUdwTAAAAAAAAAAAAAAAAAAAAAAAAB0dHVZWVtvb2/X19f///6mpqXV1df////vm1ewAAAAGdFJOUwAltTfpcLO208EAAAABYktHRA5vvTBPAAAAB3RJTUUH6QwFFREKplBWTAAABqdJREFUeNq93c+OFFUUx/EeCHud6H7A4HpCOu6NiWsWZh5Ax8YFbAdNWBPxBcCo+yGjD+CAW1ywh0Rexu6uulX3Vp269/c7f7o3Bpjhk6+H6TrU0LdXR6sDPI4/OgCyvn0I5NsDpKw3B0hZbw6Qst4cIGWLxKdskfiUHRKeskPCU/ZIdMoeiU7pkOCUDglO6ZHYlB6JTUlIaEpCQlMGJDJlQCJTRiQwZUQCUzIkLiVD4lJyJCwlR8JSCiQqpUCiUkokKKVEglImSEzKBIlJmSIhKT3yY2hKjzx7GpnSI7+8iExJyN+RKQl5FZkyIJEpAxKZMiKBKSMSmJIhcSkZEpeSI2EpORKWUiBRKQUSlVIiQSklEpQyQWJSJkhMyhQJSZkiISkzJCJlhkSkzJGAlDkSkCIg/ikC4p8iIe4pEuKeIiLeKSLinSIjziky4pyygPimLCC+KUuIa8oS4pqyiHimLCKeKcuIY8oy4phSQfxSKohfSg1xS6khbilVxCulinil1BGnlDrilNJAfFIaiE9KC3FJaSEuKU3EI6WJeKS0EYeUNuKQAiD2FACxpyCIOQVBzCkQYk2BEGsKhhhTMMSYAiK2FBCxpaCIKQVFTCkwYkmBEUsKjhhScMSQQiD6FALRpzCIOoVB1CkUok2hEG0KhyhTOESZQiK6FBLRpbCIKoVFVCk0okmhEU0KjyhSeESRokD4FAXCp2gQOkWD0CkqhE1RIWyKDiFTdAiZokS4FCXCpWgRKkWLUClqhElRI0yKHiFS9AiRYkDwFAOCp1gQOMWCwCkmBE0xIWiKDQFTbAiYYkSwFCOCpVgRKMWKQClmBEkxI0iKHQFS7AiQ4oC0UxyQdooH0kzxQJopLkgrxQVppfggjRQfpJHihNRTnJB6ihdSTfFCqiluSC3FDaml+CGVFD+kkuKILKc4IsspnshiiieymOKKLKW4Iq8u5RRf5FpO8UUWpuKMyCnOiJzijYgp3oiY4o5IKe6IlEIhL38FHr9fzFK47/0On195PL6YpVDIX4CRP1IKhbxGSvLHbQUy/snhUrg/XW9IpE/hkOcs0qVwyD8s0qWQ349nJ9+lcAg9+S6FfFqhJ79PIZFLGtmlkMifPLJNYf/5wgWvfAYg11f5j951H39+B3/cBZA/3uY/+nccJ/5oItdPi19Lkz91RV5snuQ/fN0jJ57I9svvkTT5rzyR7RX7wW/C5L93RPbPI8Xknysmj7zWQZz8fTeke0J8aJ088vqTR1czl5t8Fel/w2Ly12/6oXghaRn8IEz+nJh8DRkuUc/yn02XYGLy0Ou0ysn3X44+/75rvNb+IE3+OxdkXM/Lr3l+8stIvjSIkz91QMaQzeYn2+QXkWL7KSafnogd/jlcHiJPHn8iXkLKNe7BW9Pk26/73T/+y3/tkp38AjLdR4vJp+XrxIhMQuRL8Jc2ZLZYF5OnL8Gt18ff6f9bTJ5dvkQku6vw6Ub4AHb5ap1ZcPS5MHn2Eiwhxe2Rs8rk0Utw8xyJ/gNMy5eAlPd5bkiTJ5ev5tkeN7+uTP6+FpncsEqTt6zd7fNWpMmTy9cMmd15+0SYPLl8tc/AuVWZPLh8TZH5LURx8unOFzb59rlEafKG5WuCSPdC0+SvhMljyxdwVtSx9DVPTb5ExPvTafIfpMmf8oh4pz1NXr92F8jCtz+kyVPLF3Km2jfdz+mXrxxZCLFPHjnnTpw8s3xlyFLIMHn18gWdPVibPLJ8jchiyPBB5eTf4ZOHzoNMky+eiInlCzrZMk1eWruR5Qs6o/NImjxxCcZOGz2rTB64BGPnpkqTJ5Yv7ARY4/LVIz9XQ4bJK5cv7FRe49oNni/cT165fIEnJdfW7vYTMXjm8w3pax6ePHh6tW35As/hltfui8ZniUglvDb55vKFno0urd3wJRg95b26dp8SSO3/rTh5dPlCT94XJ48uX/B7CKTJa5Yv+N0QLJOH39fBsnavwRBx7X75vv/kExBpftn2k38oGOAraYBnuS+6DxwvwaPRmvwaDZmt3ZkBvlwHuFBPJp8breVrjYZM1u7SaEx+jYaUk58Y58gLj5i/lO0mPzE2d4GZYPetxrWbNDoEu68wrN2ssUfAG3Bp7X7CGnsE/R7F2UZ6tI0dwt5Dpo3dJ8LfbDlWGivm3dpuKY0V875z6RLMGtw76J3pDO69ANc6Y3WPCBn+wkUaq48JY3giJg3ukU8+ysgnH2ek5SvUGCYfaaTJhxr95GONbvLRxm7y4cb2iTjeWN08gLFaaYz/AWAIeJtzdhugAAAAAElFTkSuQmCC\") no-repeat;\n background-size: contain;\n pointer-events: none;\n z-index: 2147483647;\n transform: translate(-4px, -4px);\n transition: transform 0.08s ease-out;\n filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.3));\n }\n #qp-cursor.clicking {\n transform: translate(-4px, -4px) scale(0.9);\n }\n";
|
|
8
|
+
/**
|
|
9
|
+
* JavaScript to inject for cursor tracking
|
|
10
|
+
*/
|
|
11
|
+
export declare const CURSOR_SCRIPT = "\n (function() {\n if (document.getElementById('qp-cursor')) return;\n const cursor = document.createElement('div');\n cursor.id = 'qp-cursor';\n document.body.appendChild(cursor);\n\n document.addEventListener('mousemove', (e) => {\n cursor.style.left = e.clientX + 'px';\n cursor.style.top = e.clientY + 'px';\n });\n\n document.addEventListener('mousedown', () => cursor.classList.add('clicking'));\n document.addEventListener('mouseup', () => cursor.classList.remove('clicking'));\n\n // Start at center\n cursor.style.left = window.innerWidth / 2 + 'px';\n cursor.style.top = window.innerHeight / 2 + 'px';\n })();\n";
|
|
12
|
+
/**
|
|
13
|
+
* Generate ASS subtitle file header
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateASSHeader(width: number, height: number, font: string, size: number, primaryColor: string, secondaryColor: string, outlineColor: string, backColor: string, bold: boolean, outlineWidth: number, shadow: number, marginBottom: number): string;
|
|
16
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,eAAO,MAAM,UAAU,o7FAgBtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,+pBAmBzB,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,MAAM,CAcR"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS and ASS styling for QuickPeek
|
|
3
|
+
*/
|
|
4
|
+
import { CURSOR_PNG } from './cursor.js';
|
|
5
|
+
/**
|
|
6
|
+
* CSS for the custom cursor overlay
|
|
7
|
+
*/
|
|
8
|
+
export const CURSOR_CSS = `
|
|
9
|
+
#qp-cursor {
|
|
10
|
+
position: fixed;
|
|
11
|
+
width: 48px;
|
|
12
|
+
height: 56px;
|
|
13
|
+
background: url("${CURSOR_PNG}") no-repeat;
|
|
14
|
+
background-size: contain;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
z-index: 2147483647;
|
|
17
|
+
transform: translate(-4px, -4px);
|
|
18
|
+
transition: transform 0.08s ease-out;
|
|
19
|
+
filter: drop-shadow(2px 2px 2px rgba(0,0,0,0.3));
|
|
20
|
+
}
|
|
21
|
+
#qp-cursor.clicking {
|
|
22
|
+
transform: translate(-4px, -4px) scale(0.9);
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
/**
|
|
26
|
+
* JavaScript to inject for cursor tracking
|
|
27
|
+
*/
|
|
28
|
+
export const CURSOR_SCRIPT = `
|
|
29
|
+
(function() {
|
|
30
|
+
if (document.getElementById('qp-cursor')) return;
|
|
31
|
+
const cursor = document.createElement('div');
|
|
32
|
+
cursor.id = 'qp-cursor';
|
|
33
|
+
document.body.appendChild(cursor);
|
|
34
|
+
|
|
35
|
+
document.addEventListener('mousemove', (e) => {
|
|
36
|
+
cursor.style.left = e.clientX + 'px';
|
|
37
|
+
cursor.style.top = e.clientY + 'px';
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
document.addEventListener('mousedown', () => cursor.classList.add('clicking'));
|
|
41
|
+
document.addEventListener('mouseup', () => cursor.classList.remove('clicking'));
|
|
42
|
+
|
|
43
|
+
// Start at center
|
|
44
|
+
cursor.style.left = window.innerWidth / 2 + 'px';
|
|
45
|
+
cursor.style.top = window.innerHeight / 2 + 'px';
|
|
46
|
+
})();
|
|
47
|
+
`;
|
|
48
|
+
/**
|
|
49
|
+
* Generate ASS subtitle file header
|
|
50
|
+
*/
|
|
51
|
+
export function generateASSHeader(width, height, font, size, primaryColor, secondaryColor, outlineColor, backColor, bold, outlineWidth, shadow, marginBottom) {
|
|
52
|
+
return `[Script Info]
|
|
53
|
+
Title: QuickPeek Demo
|
|
54
|
+
ScriptType: v4.00+
|
|
55
|
+
PlayResX: ${width}
|
|
56
|
+
PlayResY: ${height}
|
|
57
|
+
|
|
58
|
+
[V4+ Styles]
|
|
59
|
+
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
|
60
|
+
Style: Karaoke,${font},${size},${primaryColor},${secondaryColor},${outlineColor},${backColor},${bold ? 1 : 0},0,0,0,100,100,0,0,1,${outlineWidth},${shadow},2,40,40,${marginBottom},1
|
|
61
|
+
|
|
62
|
+
[Events]
|
|
63
|
+
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;uBAKH,UAAU;;;;;;;;;;;CAWhC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;CAmB5B,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAa,EACb,MAAc,EACd,IAAY,EACZ,IAAY,EACZ,YAAoB,EACpB,cAAsB,EACtB,YAAoB,EACpB,SAAiB,EACjB,IAAa,EACb,YAAoB,EACpB,MAAc,EACd,YAAoB;IAEpB,OAAO;;;YAGG,KAAK;YACL,MAAM;;;;iBAID,IAAI,IAAI,IAAI,IAAI,YAAY,IAAI,cAAc,IAAI,YAAY,IAAI,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,wBAAwB,YAAY,IAAI,MAAM,YAAY,YAAY;;;;CAIjL,CAAC;AACF,CAAC"}
|
package/dist/tts.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Speech via Microsoft Edge TTS (free, no API key)
|
|
3
|
+
*/
|
|
4
|
+
import type { Config } from './config.js';
|
|
5
|
+
export interface WordTiming {
|
|
6
|
+
word: string;
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
}
|
|
10
|
+
export interface StepAudio {
|
|
11
|
+
duration: number;
|
|
12
|
+
words: WordTiming[];
|
|
13
|
+
}
|
|
14
|
+
export interface AudioResult {
|
|
15
|
+
voiceoverPath: string;
|
|
16
|
+
stepAudio: Map<number, StepAudio>;
|
|
17
|
+
}
|
|
18
|
+
interface Step {
|
|
19
|
+
caption: string;
|
|
20
|
+
action?: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
duration?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Synthesize speech with retry on connection failure
|
|
26
|
+
*/
|
|
27
|
+
export declare function synthesizeSpeech(text: string, outputPath: string, voice: string): Promise<void>;
|
|
28
|
+
export declare function estimateDurationFromText(text: string, wordsPerMinute?: number): number;
|
|
29
|
+
export declare function estimateWordTimings(caption: string, duration: number): WordTiming[];
|
|
30
|
+
export declare function resolveVoice(config: Config): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Calculate a hash of captions for change detection
|
|
33
|
+
*/
|
|
34
|
+
export declare function getCaptionsHash(steps: Step[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Metadata stored alongside voiceover for reuse detection
|
|
37
|
+
*/
|
|
38
|
+
export interface VoiceoverMeta {
|
|
39
|
+
captionsHash: string;
|
|
40
|
+
totalCaptionLength: number;
|
|
41
|
+
stepCount: number;
|
|
42
|
+
voice: string;
|
|
43
|
+
stepDurations: Record<number, number>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Load existing voiceover metadata if available
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadVoiceoverMeta(outputDir: string): Promise<VoiceoverMeta | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Check if existing voiceover can be reused
|
|
51
|
+
*/
|
|
52
|
+
export declare function canReuseVoiceover(steps: Step[], voice: string, existingMeta: VoiceoverMeta | null): {
|
|
53
|
+
canReuse: boolean;
|
|
54
|
+
reason?: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Load existing voiceover and reconstruct stepAudio from metadata
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadExistingVoiceover(steps: Step[], outputDir: string, meta: VoiceoverMeta, config: Config): Promise<AudioResult>;
|
|
60
|
+
export declare function generateVoiceover(steps: Step[], config: Config, workDir: string, outputDir: string, logError: (context: string, error: unknown) => Promise<void>, task: (text: string) => {
|
|
61
|
+
succeed: (text?: string) => void;
|
|
62
|
+
}, info: (text: string) => void): Promise<AudioResult>;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACnC;AAED,UAAU,IAAI;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA+BD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBrG;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,SAAM,GAAG,MAAM,CAGnF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAQnF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI1D;AAYD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAQrD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAWxF;AAWD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,IAAI,EAAE,EACb,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,aAAa,GAAG,IAAI,GACjC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA+BxC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,IAAI,EAAE,EACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,EAC5D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;IAAE,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EAC5D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC3B,OAAO,CAAC,WAAW,CAAC,CA4EtB"}
|