@spark-apps/quickpeek 1.0.1 → 1.1.1

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -101
  3. package/dist/assets/qp.wm +0 -0
  4. package/dist/highlight.css +15 -0
  5. package/dist/qp.js +6143 -0
  6. package/package.json +64 -46
  7. package/dist/api-client.d.ts +0 -61
  8. package/dist/api-client.d.ts.map +0 -1
  9. package/dist/api-client.js +0 -180
  10. package/dist/api-client.js.map +0 -1
  11. package/dist/config.d.ts +0 -69
  12. package/dist/config.d.ts.map +0 -1
  13. package/dist/config.js +0 -43
  14. package/dist/config.js.map +0 -1
  15. package/dist/crawl.d.ts +0 -18
  16. package/dist/crawl.d.ts.map +0 -1
  17. package/dist/crawl.js +0 -175
  18. package/dist/crawl.js.map +0 -1
  19. package/dist/cursor.d.ts +0 -6
  20. package/dist/cursor.d.ts.map +0 -1
  21. package/dist/cursor.js +0 -6
  22. package/dist/cursor.js.map +0 -1
  23. package/dist/ffmpeg.d.ts +0 -50
  24. package/dist/ffmpeg.d.ts.map +0 -1
  25. package/dist/ffmpeg.js +0 -224
  26. package/dist/ffmpeg.js.map +0 -1
  27. package/dist/index.d.ts +0 -15
  28. package/dist/index.d.ts.map +0 -1
  29. package/dist/index.js +0 -26
  30. package/dist/index.js.map +0 -1
  31. package/dist/prompts.d.ts +0 -13
  32. package/dist/prompts.d.ts.map +0 -1
  33. package/dist/prompts.js +0 -91
  34. package/dist/prompts.js.map +0 -1
  35. package/dist/styles.d.ts +0 -16
  36. package/dist/styles.d.ts.map +0 -1
  37. package/dist/styles.js +0 -66
  38. package/dist/styles.js.map +0 -1
  39. package/dist/tts.d.ts +0 -64
  40. package/dist/tts.d.ts.map +0 -1
  41. package/dist/tts.js +0 -236
  42. package/dist/tts.js.map +0 -1
  43. package/dist/utils.d.ts +0 -25
  44. package/dist/utils.d.ts.map +0 -1
  45. package/dist/utils.js +0 -106
  46. package/dist/utils.js.map +0 -1
  47. package/dist/voices.d.ts +0 -2
  48. package/dist/voices.d.ts.map +0 -1
  49. package/dist/voices.js +0 -73
  50. package/dist/voices.js.map +0 -1
  51. package/dist/watermark.d.ts +0 -27
  52. package/dist/watermark.d.ts.map +0 -1
  53. package/dist/watermark.js +0 -57
  54. package/dist/watermark.js.map +0 -1
package/dist/prompts.js DELETED
@@ -1,91 +0,0 @@
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
@@ -1 +0,0 @@
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 DELETED
@@ -1,16 +0,0 @@
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,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\") 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
@@ -1 +0,0 @@
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 DELETED
@@ -1,66 +0,0 @@
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
@@ -1 +0,0 @@
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 DELETED
@@ -1,64 +0,0 @@
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
package/dist/tts.d.ts.map DELETED
@@ -1 +0,0 @@
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"}
package/dist/tts.js DELETED
@@ -1,236 +0,0 @@
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 DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"tts.js","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts DELETED
@@ -1,25 +0,0 @@
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
@@ -1 +0,0 @@
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 DELETED
@@ -1,106 +0,0 @@
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
package/dist/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}