@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.
Files changed (49) hide show
  1. package/dist/api-client.d.ts +61 -0
  2. package/dist/api-client.d.ts.map +1 -0
  3. package/dist/api-client.js +180 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/config.d.ts +69 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +43 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/crawl.d.ts +18 -0
  10. package/dist/crawl.d.ts.map +1 -0
  11. package/dist/crawl.js +175 -0
  12. package/dist/crawl.js.map +1 -0
  13. package/dist/cursor.d.ts +6 -0
  14. package/dist/cursor.d.ts.map +1 -0
  15. package/dist/cursor.js +6 -0
  16. package/dist/cursor.js.map +1 -0
  17. package/dist/ffmpeg.d.ts +50 -0
  18. package/dist/ffmpeg.d.ts.map +1 -0
  19. package/dist/ffmpeg.js +224 -0
  20. package/dist/ffmpeg.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +26 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/prompts.d.ts +13 -0
  26. package/dist/prompts.d.ts.map +1 -0
  27. package/dist/prompts.js +91 -0
  28. package/dist/prompts.js.map +1 -0
  29. package/dist/styles.d.ts +16 -0
  30. package/dist/styles.d.ts.map +1 -0
  31. package/dist/styles.js +66 -0
  32. package/dist/styles.js.map +1 -0
  33. package/dist/tts.d.ts +64 -0
  34. package/dist/tts.d.ts.map +1 -0
  35. package/dist/tts.js +236 -0
  36. package/dist/tts.js.map +1 -0
  37. package/dist/utils.d.ts +25 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +106 -0
  40. package/dist/utils.js.map +1 -0
  41. package/dist/voices.d.ts +2 -0
  42. package/dist/voices.d.ts.map +1 -0
  43. package/dist/voices.js +73 -0
  44. package/dist/voices.js.map +1 -0
  45. package/dist/watermark.d.ts +27 -0
  46. package/dist/watermark.d.ts.map +1 -0
  47. package/dist/watermark.js +57 -0
  48. package/dist/watermark.js.map +1 -0
  49. 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts.js","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -0,0 +1,2 @@
1
+ export declare const LANGUAGE_VOICES: Record<string, string>;
2
+ //# sourceMappingURL=voices.d.ts.map
@@ -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
+ }