@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
package/dist/tts.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Speech via Microsoft Edge TTS (free, no API key)
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { concatMedia, generateSilence, getMediaDuration } from './ffmpeg.js';
|
|
7
|
+
import { LANGUAGE_VOICES } from './voices.js';
|
|
8
|
+
/**
|
|
9
|
+
* Estimate duration for steps without captions based on action type
|
|
10
|
+
*/
|
|
11
|
+
function estimateActionDuration(step) {
|
|
12
|
+
if (step.action === 'drag' && step.value === 'around') {
|
|
13
|
+
return 3000; // Drag around takes ~3 seconds
|
|
14
|
+
}
|
|
15
|
+
if (step.action === 'drag') {
|
|
16
|
+
return 2000; // Other drags take ~2 seconds
|
|
17
|
+
}
|
|
18
|
+
if (step.action === 'scroll') {
|
|
19
|
+
return 1500;
|
|
20
|
+
}
|
|
21
|
+
return 1000; // Default 1 second for other actions
|
|
22
|
+
}
|
|
23
|
+
let ttsInstance = null;
|
|
24
|
+
let currentVoice = null;
|
|
25
|
+
/**
|
|
26
|
+
* Create fresh TTS instance
|
|
27
|
+
*/
|
|
28
|
+
async function createTTS(voice) {
|
|
29
|
+
const { MsEdgeTTS, OUTPUT_FORMAT } = await import('msedge-tts');
|
|
30
|
+
const tts = new MsEdgeTTS();
|
|
31
|
+
await tts.setMetadata(voice, OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3);
|
|
32
|
+
return tts;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Synthesize speech with retry on connection failure
|
|
36
|
+
*/
|
|
37
|
+
export async function synthesizeSpeech(text, outputPath, voice) {
|
|
38
|
+
const maxRetries = 2;
|
|
39
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
40
|
+
try {
|
|
41
|
+
// Create new instance on first call or after failure
|
|
42
|
+
if (!ttsInstance || currentVoice !== voice || attempt > 0) {
|
|
43
|
+
ttsInstance = await createTTS(voice);
|
|
44
|
+
currentVoice = voice;
|
|
45
|
+
}
|
|
46
|
+
await ttsInstance.toFile(outputPath, text);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
ttsInstance = null; // Reset on failure
|
|
51
|
+
if (attempt === maxRetries)
|
|
52
|
+
throw err;
|
|
53
|
+
await new Promise((r) => setTimeout(r, 500)); // Wait before retry
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function estimateDurationFromText(text, wordsPerMinute = 150) {
|
|
58
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
59
|
+
return Math.max(Math.round((words / wordsPerMinute) * 60 * 1000), 1500);
|
|
60
|
+
}
|
|
61
|
+
export function estimateWordTimings(caption, duration) {
|
|
62
|
+
const words = caption.split(/\s+/).filter((w) => w.length > 0);
|
|
63
|
+
const wordDuration = duration / words.length;
|
|
64
|
+
return words.map((word, i) => ({
|
|
65
|
+
word,
|
|
66
|
+
start: Math.round(i * wordDuration),
|
|
67
|
+
end: Math.round((i + 1) * wordDuration),
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
export function resolveVoice(config) {
|
|
71
|
+
if (config.audio?.voice)
|
|
72
|
+
return config.audio.voice;
|
|
73
|
+
if (config.lang)
|
|
74
|
+
return LANGUAGE_VOICES[config.lang] || null;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
async function concatenateAudio(inputs, output, workDir) {
|
|
78
|
+
const listContent = inputs.map((f) => `file '${resolve(f)}'`).join('\n');
|
|
79
|
+
const listPath = join(workDir, 'concat.txt');
|
|
80
|
+
await writeFile(listPath, listContent);
|
|
81
|
+
await concatMedia(listPath, output);
|
|
82
|
+
// Clean up temp file
|
|
83
|
+
const { unlink } = await import('node:fs/promises');
|
|
84
|
+
await unlink(listPath).catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Calculate a hash of captions for change detection
|
|
88
|
+
*/
|
|
89
|
+
export function getCaptionsHash(steps) {
|
|
90
|
+
const captions = steps.map((s) => s.caption || '').join('|');
|
|
91
|
+
// Simple hash - sum of char codes
|
|
92
|
+
let hash = 0;
|
|
93
|
+
for (let i = 0; i < captions.length; i++) {
|
|
94
|
+
hash = ((hash << 5) - hash + captions.charCodeAt(i)) | 0;
|
|
95
|
+
}
|
|
96
|
+
return hash.toString(16);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Load existing voiceover metadata if available
|
|
100
|
+
*/
|
|
101
|
+
export async function loadVoiceoverMeta(outputDir) {
|
|
102
|
+
const metaPath = join(outputDir, 'voiceover.json');
|
|
103
|
+
try {
|
|
104
|
+
const { existsSync } = await import('node:fs');
|
|
105
|
+
if (!existsSync(metaPath))
|
|
106
|
+
return null;
|
|
107
|
+
const { readFile } = await import('node:fs/promises');
|
|
108
|
+
const content = await readFile(metaPath, 'utf-8');
|
|
109
|
+
return JSON.parse(content);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Save voiceover metadata for future reuse detection
|
|
117
|
+
*/
|
|
118
|
+
async function saveVoiceoverMeta(outputDir, meta) {
|
|
119
|
+
const metaPath = join(outputDir, 'voiceover.json');
|
|
120
|
+
const { writeFile } = await import('node:fs/promises');
|
|
121
|
+
await writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if existing voiceover can be reused
|
|
125
|
+
*/
|
|
126
|
+
export function canReuseVoiceover(steps, voice, existingMeta) {
|
|
127
|
+
if (!existingMeta) {
|
|
128
|
+
return { canReuse: false, reason: 'No existing voiceover found' };
|
|
129
|
+
}
|
|
130
|
+
// Check voice match
|
|
131
|
+
if (existingMeta.voice !== voice) {
|
|
132
|
+
return { canReuse: false, reason: `Voice changed (${existingMeta.voice} → ${voice})` };
|
|
133
|
+
}
|
|
134
|
+
// Check step count
|
|
135
|
+
if (existingMeta.stepCount !== steps.length) {
|
|
136
|
+
return { canReuse: false, reason: `Step count changed (${existingMeta.stepCount} → ${steps.length})` };
|
|
137
|
+
}
|
|
138
|
+
// Check captions hash for exact match
|
|
139
|
+
const currentHash = getCaptionsHash(steps);
|
|
140
|
+
if (existingMeta.captionsHash === currentHash) {
|
|
141
|
+
return { canReuse: true };
|
|
142
|
+
}
|
|
143
|
+
// Check total caption length - allow 10% variance
|
|
144
|
+
const currentLength = steps.reduce((sum, s) => sum + (s.caption?.length || 0), 0);
|
|
145
|
+
const lengthDiff = Math.abs(currentLength - existingMeta.totalCaptionLength);
|
|
146
|
+
const threshold = existingMeta.totalCaptionLength * 0.1;
|
|
147
|
+
if (lengthDiff <= threshold) {
|
|
148
|
+
return { canReuse: true, reason: 'Captions similar enough' };
|
|
149
|
+
}
|
|
150
|
+
return { canReuse: false, reason: 'Captions changed significantly' };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Load existing voiceover and reconstruct stepAudio from metadata
|
|
154
|
+
*/
|
|
155
|
+
export async function loadExistingVoiceover(steps, outputDir, meta, config) {
|
|
156
|
+
const voiceoverPath = join(outputDir, 'voiceover.wav');
|
|
157
|
+
const stepAudio = new Map();
|
|
158
|
+
for (let i = 0; i < steps.length; i++) {
|
|
159
|
+
const step = steps[i];
|
|
160
|
+
const stepNum = i + 1;
|
|
161
|
+
const duration = meta.stepDurations[stepNum] || config.timing.minStepDuration;
|
|
162
|
+
stepAudio.set(stepNum, {
|
|
163
|
+
duration,
|
|
164
|
+
words: estimateWordTimings(step.caption || '', duration),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return { voiceoverPath, stepAudio };
|
|
168
|
+
}
|
|
169
|
+
export async function generateVoiceover(steps, config, workDir, outputDir, logError, task, info) {
|
|
170
|
+
const voice = resolveVoice(config);
|
|
171
|
+
if (!voice) {
|
|
172
|
+
const stepAudio = new Map();
|
|
173
|
+
for (let i = 0; i < steps.length; i++) {
|
|
174
|
+
const step = steps[i];
|
|
175
|
+
const duration = step.caption ? estimateDurationFromText(step.caption) : estimateActionDuration(step);
|
|
176
|
+
stepAudio.set(i + 1, { duration, words: step.caption ? estimateWordTimings(step.caption, duration) : [] });
|
|
177
|
+
}
|
|
178
|
+
info('No voice configured');
|
|
179
|
+
return { voiceoverPath: '', stepAudio };
|
|
180
|
+
}
|
|
181
|
+
const audioDir = join(workDir, 'audio');
|
|
182
|
+
await mkdir(audioDir, { recursive: true });
|
|
183
|
+
const segments = [];
|
|
184
|
+
const stepAudio = new Map();
|
|
185
|
+
const ttsTask = task('Generating voiceover');
|
|
186
|
+
for (let idx = 0; idx < steps.length; idx++) {
|
|
187
|
+
const step = steps[idx];
|
|
188
|
+
const stepNum = idx + 1;
|
|
189
|
+
// Empty caption = generate silence to keep audio/video in sync
|
|
190
|
+
if (!step.caption) {
|
|
191
|
+
const silenceDuration = estimateActionDuration(step);
|
|
192
|
+
const silencePath = join(audioDir, `step-${stepNum}-silence.mp3`);
|
|
193
|
+
await generateSilence(silencePath, silenceDuration);
|
|
194
|
+
segments.push(silencePath);
|
|
195
|
+
stepAudio.set(stepNum, { duration: silenceDuration, words: [] });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const audioPath = join(audioDir, `step-${stepNum}.mp3`);
|
|
199
|
+
if (segments.length > 0)
|
|
200
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
201
|
+
try {
|
|
202
|
+
await synthesizeSpeech(step.caption, audioPath, voice);
|
|
203
|
+
segments.push(audioPath);
|
|
204
|
+
const audioDuration = await getMediaDuration(audioPath);
|
|
205
|
+
// Use raw audio duration - audio drives timing, no artificial padding
|
|
206
|
+
const stepDuration = Math.max(audioDuration, config.timing.minStepDuration);
|
|
207
|
+
stepAudio.set(stepNum, { duration: stepDuration, words: estimateWordTimings(step.caption, audioDuration) });
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
await logError(`TTS Step ${stepNum}`, err);
|
|
211
|
+
stepAudio.set(stepNum, { duration: config.timing.minStepDuration, words: [] });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const stepsWithCaptions = steps.filter((s) => s.caption).length;
|
|
215
|
+
ttsTask.succeed(`${segments.length}/${stepsWithCaptions} clips`);
|
|
216
|
+
if (segments.length === 0) {
|
|
217
|
+
return { voiceoverPath: '', stepAudio };
|
|
218
|
+
}
|
|
219
|
+
const concatTask = task('Combining audio');
|
|
220
|
+
// Save voiceover to output directory for reuse
|
|
221
|
+
await mkdir(outputDir, { recursive: true });
|
|
222
|
+
const voiceoverPath = join(outputDir, 'voiceover.wav');
|
|
223
|
+
await concatenateAudio(segments, voiceoverPath, workDir);
|
|
224
|
+
concatTask.succeed('Done');
|
|
225
|
+
// Save metadata for future reuse detection
|
|
226
|
+
const meta = {
|
|
227
|
+
captionsHash: getCaptionsHash(steps),
|
|
228
|
+
totalCaptionLength: steps.reduce((sum, s) => sum + (s.caption?.length || 0), 0),
|
|
229
|
+
stepCount: steps.length,
|
|
230
|
+
voice,
|
|
231
|
+
stepDurations: Object.fromEntries(Array.from(stepAudio.entries()).map(([k, v]) => [k, v.duration])),
|
|
232
|
+
};
|
|
233
|
+
await saveVoiceoverMeta(outputDir, meta);
|
|
234
|
+
return { voiceoverPath, stepAudio };
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=tts.js.map
|
package/dist/tts.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.js","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for QuickPeek
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Language code to full name mapping
|
|
6
|
+
*/
|
|
7
|
+
export declare const LANGUAGE_NAMES: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Get full language name from ISO code
|
|
10
|
+
*/
|
|
11
|
+
export declare function getLanguageName(lang?: string): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Convert hex color (#RRGGBB) to ASS subtitle format (&HAABBGGRR)
|
|
14
|
+
*/
|
|
15
|
+
export declare function hexToASS(hex: string, alpha?: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* Convert rgba() or rgb() color string to hex
|
|
18
|
+
*/
|
|
19
|
+
export declare function rgbaToHex(color: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Convert text to Title Case, keeping prepositions/articles lowercase
|
|
22
|
+
* First word is always capitalized
|
|
23
|
+
*/
|
|
24
|
+
export declare function toTitleCase(text: string): string;
|
|
25
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAajD,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,CAOvD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ/C;AAsCD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYhD"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for QuickPeek
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Language code to full name mapping
|
|
6
|
+
*/
|
|
7
|
+
export const LANGUAGE_NAMES = {
|
|
8
|
+
ar: 'Arabic',
|
|
9
|
+
es: 'Spanish',
|
|
10
|
+
fr: 'French',
|
|
11
|
+
de: 'German',
|
|
12
|
+
zh: 'Chinese',
|
|
13
|
+
ja: 'Japanese',
|
|
14
|
+
ko: 'Korean',
|
|
15
|
+
pt: 'Portuguese',
|
|
16
|
+
ru: 'Russian',
|
|
17
|
+
hi: 'Hindi',
|
|
18
|
+
tr: 'Turkish',
|
|
19
|
+
it: 'Italian',
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Get full language name from ISO code
|
|
23
|
+
*/
|
|
24
|
+
export function getLanguageName(lang) {
|
|
25
|
+
if (!lang || lang === 'en')
|
|
26
|
+
return null;
|
|
27
|
+
return LANGUAGE_NAMES[lang] || lang.toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert hex color (#RRGGBB) to ASS subtitle format (&HAABBGGRR)
|
|
31
|
+
*/
|
|
32
|
+
export function hexToASS(hex, alpha = 0) {
|
|
33
|
+
const clean = hex.replace('#', '');
|
|
34
|
+
const r = clean.slice(0, 2);
|
|
35
|
+
const g = clean.slice(2, 4);
|
|
36
|
+
const b = clean.slice(4, 6);
|
|
37
|
+
const a = alpha.toString(16).padStart(2, '0').toUpperCase();
|
|
38
|
+
return `&H${a}${b}${g}${r}`.toUpperCase();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Convert rgba() or rgb() color string to hex
|
|
42
|
+
*/
|
|
43
|
+
export function rgbaToHex(color) {
|
|
44
|
+
if (color.startsWith('#'))
|
|
45
|
+
return color;
|
|
46
|
+
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
47
|
+
if (!match)
|
|
48
|
+
return '#000000';
|
|
49
|
+
const r = parseInt(match[1], 10).toString(16).padStart(2, '0');
|
|
50
|
+
const g = parseInt(match[2], 10).toString(16).padStart(2, '0');
|
|
51
|
+
const b = parseInt(match[3], 10).toString(16).padStart(2, '0');
|
|
52
|
+
return `#${r}${g}${b}`.toUpperCase();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Words to keep lowercase in title case (prepositions, articles, conjunctions)
|
|
56
|
+
*/
|
|
57
|
+
const LOWERCASE_WORDS = new Set([
|
|
58
|
+
'a',
|
|
59
|
+
'an',
|
|
60
|
+
'the',
|
|
61
|
+
'and',
|
|
62
|
+
'but',
|
|
63
|
+
'or',
|
|
64
|
+
'nor',
|
|
65
|
+
'for',
|
|
66
|
+
'yet',
|
|
67
|
+
'so',
|
|
68
|
+
'at',
|
|
69
|
+
'by',
|
|
70
|
+
'in',
|
|
71
|
+
'of',
|
|
72
|
+
'on',
|
|
73
|
+
'to',
|
|
74
|
+
'up',
|
|
75
|
+
'as',
|
|
76
|
+
'if',
|
|
77
|
+
'off',
|
|
78
|
+
'per',
|
|
79
|
+
'via',
|
|
80
|
+
'from',
|
|
81
|
+
'into',
|
|
82
|
+
'onto',
|
|
83
|
+
'upon',
|
|
84
|
+
'with',
|
|
85
|
+
'over',
|
|
86
|
+
'under',
|
|
87
|
+
'than',
|
|
88
|
+
]);
|
|
89
|
+
/**
|
|
90
|
+
* Convert text to Title Case, keeping prepositions/articles lowercase
|
|
91
|
+
* First word is always capitalized
|
|
92
|
+
*/
|
|
93
|
+
export function toTitleCase(text) {
|
|
94
|
+
return text.replace(/\w+/g, (word, index) => {
|
|
95
|
+
// Always capitalize first word
|
|
96
|
+
if (index === 0) {
|
|
97
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
98
|
+
}
|
|
99
|
+
// Keep prepositions/articles lowercase
|
|
100
|
+
if (LOWERCASE_WORDS.has(word.toLowerCase())) {
|
|
101
|
+
return word.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/dist/voices.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voices.d.ts","sourceRoot":"","sources":["../src/voices.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAoElD,CAAC"}
|
package/dist/voices.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Default Edge TTS voice per language
|
|
2
|
+
// Format: ISO 639-1 language code -> Edge TTS voice name
|
|
3
|
+
// Voice list: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts
|
|
4
|
+
export const LANGUAGE_VOICES = {
|
|
5
|
+
en: 'en-GB-SoniaNeural',
|
|
6
|
+
ar: 'ar-LB-LaylaNeural',
|
|
7
|
+
es: 'es-ES-ElviraNeural',
|
|
8
|
+
fr: 'fr-FR-DeniseNeural',
|
|
9
|
+
de: 'de-DE-KatjaNeural',
|
|
10
|
+
it: 'it-IT-ElsaNeural',
|
|
11
|
+
pt: 'pt-BR-FranciscaNeural',
|
|
12
|
+
ru: 'ru-RU-SvetlanaNeural',
|
|
13
|
+
zh: 'zh-CN-XiaoxiaoNeural',
|
|
14
|
+
ja: 'ja-JP-NanamiNeural',
|
|
15
|
+
ko: 'ko-KR-SunHiNeural',
|
|
16
|
+
hi: 'hi-IN-SwaraNeural',
|
|
17
|
+
tr: 'tr-TR-EmelNeural',
|
|
18
|
+
nl: 'nl-NL-ColetteNeural',
|
|
19
|
+
pl: 'pl-PL-AgnieszkaNeural',
|
|
20
|
+
sv: 'sv-SE-SofieNeural',
|
|
21
|
+
he: 'he-IL-HilaNeural',
|
|
22
|
+
th: 'th-TH-PremwadeeNeural',
|
|
23
|
+
vi: 'vi-VN-HoaiMyNeural',
|
|
24
|
+
id: 'id-ID-GadisNeural',
|
|
25
|
+
ms: 'ms-MY-YasminNeural',
|
|
26
|
+
fil: 'fil-PH-BlessicaNeural',
|
|
27
|
+
uk: 'uk-UA-PolinaNeural',
|
|
28
|
+
cs: 'cs-CZ-VlastaNeural',
|
|
29
|
+
el: 'el-GR-AthinaNeural',
|
|
30
|
+
ro: 'ro-RO-AlinaNeural',
|
|
31
|
+
hu: 'hu-HU-NoemiNeural',
|
|
32
|
+
da: 'da-DK-ChristelNeural',
|
|
33
|
+
fi: 'fi-FI-NooraNeural',
|
|
34
|
+
no: 'nb-NO-PernilleNeural',
|
|
35
|
+
sk: 'sk-SK-ViktoriaNeural',
|
|
36
|
+
bg: 'bg-BG-KalinaNeural',
|
|
37
|
+
hr: 'hr-HR-GabrijelaNeural',
|
|
38
|
+
sl: 'sl-SI-PetraNeural',
|
|
39
|
+
et: 'et-EE-AnuNeural',
|
|
40
|
+
lv: 'lv-LV-EveritaNeural',
|
|
41
|
+
lt: 'lt-LT-OnaNeural',
|
|
42
|
+
bn: 'bn-IN-TanishaaNeural',
|
|
43
|
+
ta: 'ta-IN-PallaviNeural',
|
|
44
|
+
te: 'te-IN-ShrutiNeural',
|
|
45
|
+
mr: 'mr-IN-AarohiNeural',
|
|
46
|
+
gu: 'gu-IN-DhwaniNeural',
|
|
47
|
+
kn: 'kn-IN-SapnaNeural',
|
|
48
|
+
ml: 'ml-IN-SobhanaNeural',
|
|
49
|
+
ur: 'ur-PK-UzmaNeural',
|
|
50
|
+
fa: 'fa-IR-DilaraNeural',
|
|
51
|
+
sw: 'sw-KE-ZuriNeural',
|
|
52
|
+
af: 'af-ZA-AdriNeural',
|
|
53
|
+
ca: 'ca-ES-JoanaNeural',
|
|
54
|
+
cy: 'cy-GB-NiaNeural',
|
|
55
|
+
ga: 'ga-IE-OrlaNeural',
|
|
56
|
+
mt: 'mt-MT-GraceNeural',
|
|
57
|
+
is: 'is-IS-GudrunNeural',
|
|
58
|
+
mk: 'mk-MK-MarijaNeural',
|
|
59
|
+
sr: 'sr-RS-SophieNeural',
|
|
60
|
+
bs: 'bs-BA-VesnaNeural',
|
|
61
|
+
sq: 'sq-AL-AnilaNeural',
|
|
62
|
+
am: 'am-ET-MekdesNeural',
|
|
63
|
+
az: 'az-AZ-BabekNeural',
|
|
64
|
+
ka: 'ka-GE-EkaNeural',
|
|
65
|
+
kk: 'kk-KZ-AigulNeural',
|
|
66
|
+
mn: 'mn-MN-YesUINeural',
|
|
67
|
+
ne: 'ne-NP-HemkalaNeural',
|
|
68
|
+
ps: 'ps-AF-LatifaNeural',
|
|
69
|
+
si: 'si-LK-ThiliniNeural',
|
|
70
|
+
so: 'so-SO-UbaxNeural',
|
|
71
|
+
zu: 'zu-ZA-ThandoNeural',
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=voices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voices.js","sourceRoot":"","sources":["../src/voices.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,yDAAyD;AACzD,2GAA2G;AAE3G,MAAM,CAAC,MAAM,eAAe,GAA2B;IACrD,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,oBAAoB;IACxB,GAAG,EAAE,uBAAuB;IAC5B,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,oBAAoB;CACzB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg watermark filter generation for QuickPeek
|
|
3
|
+
* Applies watermarks to free tier videos longer than 20 seconds
|
|
4
|
+
*/
|
|
5
|
+
import type { UserTier } from './config.js';
|
|
6
|
+
export interface WatermarkOptions {
|
|
7
|
+
tier: UserTier;
|
|
8
|
+
videoDuration: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Build FFmpeg drawtext filters for watermarks
|
|
12
|
+
* Returns empty string if no watermark needed (pro tier or short video)
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildWatermarkFilters(opts: WatermarkOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* Check if watermark should be applied based on tier and duration
|
|
17
|
+
*/
|
|
18
|
+
export declare function shouldApplyWatermark(tier: UserTier, videoDurationSeconds: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Get watermark configuration for display purposes
|
|
21
|
+
*/
|
|
22
|
+
export declare function getWatermarkInfo(): {
|
|
23
|
+
threshold: number;
|
|
24
|
+
cornerText: string;
|
|
25
|
+
endText: string;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=watermark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watermark.d.ts","sourceRoot":"","sources":["../src/watermark.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAWD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAmCpE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAE1F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,CAMA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg watermark filter generation for QuickPeek
|
|
3
|
+
* Applies watermarks to free tier videos longer than 20 seconds
|
|
4
|
+
*/
|
|
5
|
+
// Watermark threshold: only apply to videos longer than 20 seconds
|
|
6
|
+
const WATERMARK_THRESHOLD_SECONDS = 20;
|
|
7
|
+
// End text appears in the last 3 seconds
|
|
8
|
+
const END_TEXT_DURATION = 3;
|
|
9
|
+
// Fade-in duration for end text
|
|
10
|
+
const END_TEXT_FADE_IN = 1;
|
|
11
|
+
/**
|
|
12
|
+
* Build FFmpeg drawtext filters for watermarks
|
|
13
|
+
* Returns empty string if no watermark needed (pro tier or short video)
|
|
14
|
+
*/
|
|
15
|
+
export function buildWatermarkFilters(opts) {
|
|
16
|
+
const { tier, videoDuration } = opts;
|
|
17
|
+
// No watermark for pro tier
|
|
18
|
+
if (tier === 'pro') {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
// No watermark for videos 20 seconds or shorter
|
|
22
|
+
if (videoDuration <= WATERMARK_THRESHOLD_SECONDS) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
const filters = [];
|
|
26
|
+
// Corner "qp" watermark - always visible (bottom-right)
|
|
27
|
+
// White text at 30% opacity, small and subtle
|
|
28
|
+
filters.push(`drawtext=text='qp':fontsize=24:fontcolor=white@0.3:x=w-tw-20:y=h-th-20`);
|
|
29
|
+
// End text - "Video made with QuickPeek" in last 3 seconds
|
|
30
|
+
// Centered at bottom, with fade-in effect
|
|
31
|
+
const endTextStart = Math.max(0, videoDuration - END_TEXT_DURATION);
|
|
32
|
+
const fadeEndTime = endTextStart + END_TEXT_FADE_IN;
|
|
33
|
+
// Build alpha expression for fade-in:
|
|
34
|
+
// - Before endTextStart: 0 (invisible)
|
|
35
|
+
// - During fade-in: linear ramp from 0 to 0.6
|
|
36
|
+
// - After fade-in: 0.6 (full opacity)
|
|
37
|
+
const alphaExpr = `if(lt(t,${endTextStart}),0,if(lt(t,${fadeEndTime}),(t-${endTextStart})*0.6,0.6))`;
|
|
38
|
+
filters.push(`drawtext=text='Video made with QuickPeek':fontsize=32:fontcolor=white:x=(w-tw)/2:y=h-80:alpha='${alphaExpr}'`);
|
|
39
|
+
return filters.join(',');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if watermark should be applied based on tier and duration
|
|
43
|
+
*/
|
|
44
|
+
export function shouldApplyWatermark(tier, videoDurationSeconds) {
|
|
45
|
+
return tier === 'free' && videoDurationSeconds > WATERMARK_THRESHOLD_SECONDS;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get watermark configuration for display purposes
|
|
49
|
+
*/
|
|
50
|
+
export function getWatermarkInfo() {
|
|
51
|
+
return {
|
|
52
|
+
threshold: WATERMARK_THRESHOLD_SECONDS,
|
|
53
|
+
cornerText: 'qp',
|
|
54
|
+
endText: 'Video made with QuickPeek',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=watermark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watermark.js","sourceRoot":"","sources":["../src/watermark.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,mEAAmE;AACnE,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAEvC,yCAAyC;AACzC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,gCAAgC;AAChC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAsB;IAC1D,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAErC,4BAA4B;IAC5B,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,gDAAgD;IAChD,IAAI,aAAa,IAAI,2BAA2B,EAAE,CAAC;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,wDAAwD;IACxD,8CAA8C;IAC9C,OAAO,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;IAEvF,2DAA2D;IAC3D,0CAA0C;IAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,iBAAiB,CAAC,CAAC;IACpE,MAAM,WAAW,GAAG,YAAY,GAAG,gBAAgB,CAAC;IAEpD,sCAAsC;IACtC,uCAAuC;IACvC,8CAA8C;IAC9C,sCAAsC;IACtC,MAAM,SAAS,GAAG,WAAW,YAAY,eAAe,WAAW,QAAQ,YAAY,aAAa,CAAC;IAErG,OAAO,CAAC,IAAI,CACV,kGAAkG,SAAS,GAAG,CAC/G,CAAC;IAEF,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,oBAA4B;IAC/E,OAAO,IAAI,KAAK,MAAM,IAAI,oBAAoB,GAAG,2BAA2B,CAAC;AAC/E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAK9B,OAAO;QACL,SAAS,EAAE,2BAA2B;QACtC,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,2BAA2B;KACrC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spark-apps/quickpeek",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core library for QuickPeek - AI-powered demo video generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"clean": "rm -rf dist"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"quickpeek",
|
|
24
|
+
"demo",
|
|
25
|
+
"video",
|
|
26
|
+
"ai",
|
|
27
|
+
"tts",
|
|
28
|
+
"ffmpeg"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"msedge-tts": "^1.3.4"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"playwright": "^1.40.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"playwright": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|