@spark-apps/quickpeek 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +4 -0
- package/dist/assets/qp.wm +0 -0
- package/dist/highlight.css +0 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.mjs +199 -198
- package/dist/mcp-tools.mjs +5909 -0
- package/dist/qp.js +1316 -1154
- package/dist/{voices-BtYZey4Y.d.mts → scraping-BK8pIJhm.d.mts} +30 -27
- package/dist/web.d.mts +2 -1
- package/dist/web.mjs +129 -176
- package/mcp.mjs +75 -0
- package/package.json +80 -76
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -182,6 +182,10 @@ Edit `demo/plan.json` to customize your demo. Each step uses one of these action
|
|
|
182
182
|
| `demo/demo.mp4` | Final video |
|
|
183
183
|
| `demo/plan.json` | Editable script (re-run to apply changes) |
|
|
184
184
|
|
|
185
|
+
> `plan.json` is also accepted by [VidLet](https://vidlet.app)'s `create_project`
|
|
186
|
+
> MCP tool, which turns the steps into a `.vidlet` timeline project for further
|
|
187
|
+
> editing and narration.
|
|
188
|
+
|
|
185
189
|
## 🌱 Support & Contributions
|
|
186
190
|
|
|
187
191
|
⭐ **Star the repo** & I power up like Mario 🍄
|
package/dist/assets/qp.wm
CHANGED
|
File without changes
|
package/dist/highlight.css
CHANGED
|
File without changes
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { U as UserTier, C as Config } from './
|
|
2
|
-
export { A as AIError, a as AIResponse, b as AIResult, c as CONFIG_FILE, d as CaptionStyle, e as CaptionWordStyle, D as DEFAULT_CAPTIONS, f as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, g as ElementInfo, h as ElementType, F as FEATURES, H as HighlightMode, I as INTERACTIVE_SELECTORS,
|
|
1
|
+
import { U as UserTier, C as Config } from './scraping-BK8pIJhm.mjs';
|
|
2
|
+
export { A as AIError, a as AIResponse, b as AIResult, c as CONFIG_FILE, d as CaptionStyle, e as CaptionWordStyle, D as DEFAULT_CAPTIONS, f as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, g as ElementInfo, h as ElementType, F as FEATURES, H as HighlightMode, I as INTERACTIVE_SELECTORS, P as Plan, R as RateLimitInfo, S as SparkStatus, i as SparkSubscription, j as SparkTrial, k as Step, l as SuggestedAction, T as Tier, V as VERSION, m as buildSystemPrompt, n as buildUserPrompt, o as callAI, p as callAIViaRelay, q as crawlPage, r as getErrorMessage, s as getLanguageName, t as getStatus, u as getStatusCached, v as getTierByEmail, w as hasAccess, x as hasTrialRemaining, y as isCanceling, z as isExcludedLink, B as isPaid, G as isVerified, J as normalizeUrl, K as parseAIPlanResponse, L as parsePartial, M as pricingUrl, N as rgbaToHex, O as shouldSkipLink, Q as showPaywall, W as toTitleCase } from './scraping-BK8pIJhm.mjs';
|
|
3
|
+
export { WIDE_VOICES as LANGUAGE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Billing utilities — watermark decisions and upgrade URL generation
|
|
@@ -171,9 +172,6 @@ declare function buildStepTransitions(steps: {
|
|
|
171
172
|
*/
|
|
172
173
|
declare function applyTransitions(opts: ApplyTransitionsOptions): Promise<void>;
|
|
173
174
|
|
|
174
|
-
/**
|
|
175
|
-
* CSS and ASS styling for QuickPeek
|
|
176
|
-
*/
|
|
177
175
|
declare const CURSOR_CSS = "\n #qp-cursor {\n position: fixed;\n width: 32px;\n height: 38px;\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(-3px, -3px);\n transition: left 800ms ease-out, top 800ms ease-out, transform 0.08s ease-out;\n filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3));\n }\n #qp-cursor.clicking {\n transform: translate(-3px, -3px) scale(0.9);\n }\n";
|
|
178
176
|
/**
|
|
179
177
|
* JavaScript to inject for cursor tracking
|
package/dist/index.mjs
CHANGED
|
@@ -9,18 +9,12 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/lib/utils.ts
|
|
12
|
+
import { hexToAss } from "@spark-apps/video-kit";
|
|
13
|
+
import { windowsDrivePathToWsl } from "@spark-apps/video-kit";
|
|
12
14
|
function getLanguageName(lang) {
|
|
13
15
|
if (!lang || lang === "en") return null;
|
|
14
16
|
return LANGUAGE_NAMES[lang] || lang.toUpperCase();
|
|
15
17
|
}
|
|
16
|
-
function hexToASS(hex, alpha = 0) {
|
|
17
|
-
const clean = hex.replace("#", "");
|
|
18
|
-
const r = clean.slice(0, 2);
|
|
19
|
-
const g = clean.slice(2, 4);
|
|
20
|
-
const b = clean.slice(4, 6);
|
|
21
|
-
const a = alpha.toString(16).padStart(2, "0").toUpperCase();
|
|
22
|
-
return `&H${a}${b}${g}${r}`.toUpperCase();
|
|
23
|
-
}
|
|
24
18
|
function rgbaToHex(color) {
|
|
25
19
|
if (color.startsWith("#")) return color;
|
|
26
20
|
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
@@ -30,12 +24,6 @@ function rgbaToHex(color) {
|
|
|
30
24
|
const b = parseInt(match[3], 10).toString(16).padStart(2, "0");
|
|
31
25
|
return `#${r}${g}${b}`.toUpperCase();
|
|
32
26
|
}
|
|
33
|
-
function windowsPathToWSL(path) {
|
|
34
|
-
const driveMatch = /^([A-Z]):[/\\]/i.exec(path);
|
|
35
|
-
if (!driveMatch) return null;
|
|
36
|
-
const drive = driveMatch[1].toLowerCase();
|
|
37
|
-
return `/mnt/${drive}/${path.slice(3).replace(/\\/g, "/")}`;
|
|
38
|
-
}
|
|
39
27
|
function parsePartial(partial) {
|
|
40
28
|
if (!partial) return {};
|
|
41
29
|
const parts = partial.split("-");
|
|
@@ -147,10 +135,10 @@ var init_watermark = __esm({
|
|
|
147
135
|
import { spawn } from "child_process";
|
|
148
136
|
function toWSLPath(path) {
|
|
149
137
|
if (!isWindowsRuntime) return path;
|
|
150
|
-
return
|
|
138
|
+
return windowsDrivePathToWsl(path) ?? path.replace(/\\/g, "/");
|
|
151
139
|
}
|
|
152
140
|
function convertArgToWSL(arg) {
|
|
153
|
-
const wslPath =
|
|
141
|
+
const wslPath = windowsDrivePathToWsl(arg);
|
|
154
142
|
if (wslPath) return wslPath;
|
|
155
143
|
if (arg.includes("\\")) return arg.replace(/\\/g, "/");
|
|
156
144
|
return arg;
|
|
@@ -186,6 +174,7 @@ var init_spawn = __esm({
|
|
|
186
174
|
});
|
|
187
175
|
|
|
188
176
|
// src/lib/ffmpeg/compose.ts
|
|
177
|
+
import { MASTER_CHAIN } from "@spark-apps/video-kit";
|
|
189
178
|
function composeVideo(opts) {
|
|
190
179
|
return new Promise((resolve3, reject) => {
|
|
191
180
|
const {
|
|
@@ -246,20 +235,24 @@ function composeVideo(opts) {
|
|
|
246
235
|
`[1:a]apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn}[voice]`,
|
|
247
236
|
// Music: fade in and fade out
|
|
248
237
|
`[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]`,
|
|
249
|
-
|
|
238
|
+
// Normalise the MIX and hard-limit its true peak - ported from
|
|
239
|
+
// VidLet, where single-pass loudnorm alone measured +0.9 dBTP on
|
|
240
|
+
// sharp TTS consonants. -14 LUFS is the YouTube/IG/TikTok playback
|
|
241
|
+
// target, so the platform leaves the audio untouched.
|
|
242
|
+
`[voice][music]amix=inputs=2:duration=longest,${MASTER_CHAIN}[aout]`
|
|
250
243
|
].join(";");
|
|
251
244
|
args.push("-vf", vfStr);
|
|
252
245
|
args.push("-filter_complex", complexFilter, "-map", "0:v", "-map", "[aout]");
|
|
253
246
|
args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
|
|
254
247
|
args.push("-c:a", "aac", "-b:a", audioBitrate);
|
|
255
248
|
} else if (hasVoiceover) {
|
|
256
|
-
const af = `apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn}`;
|
|
249
|
+
const af = `apad=whole_dur=${voiceoverDuration},afade=t=in:st=0:d=${fadeIn},${MASTER_CHAIN}`;
|
|
257
250
|
args.push("-vf", vfStr);
|
|
258
251
|
args.push("-map", "0:v", "-map", "1:a", "-af", af);
|
|
259
252
|
args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
|
|
260
253
|
args.push("-c:a", "aac", "-b:a", audioBitrate);
|
|
261
254
|
} else if (hasMusic) {
|
|
262
|
-
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}`;
|
|
255
|
+
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},${MASTER_CHAIN}`;
|
|
263
256
|
args.push("-vf", vfStr);
|
|
264
257
|
args.push("-map", "0:v", "-map", "1:a", "-af", musicFilter);
|
|
265
258
|
args.push("-c:v", "libx264", "-preset", preset, "-crf", String(crf), "-pix_fmt", "yuv420p");
|
|
@@ -531,14 +524,13 @@ function applyTransitions(opts) {
|
|
|
531
524
|
filterParts.push(`[0:v]trim=start=${start.toFixed(3)}:end=${end.toFixed(3)},setpts=PTS-STARTPTS[v${i}]`);
|
|
532
525
|
}
|
|
533
526
|
let currentV = "v0";
|
|
527
|
+
let chainDuration = boundaries[1] - boundaries[0];
|
|
534
528
|
for (let i = 0; i < sorted.length; i++) {
|
|
535
529
|
const trans = sorted[i];
|
|
536
530
|
const duration = trans.duration ?? 0.3;
|
|
537
531
|
const nextIdx = i + 1;
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
const segDuration = segEnd - segStart;
|
|
541
|
-
const offset = Math.max(0, segDuration - duration);
|
|
532
|
+
const offset = Math.max(0, chainDuration - duration);
|
|
533
|
+
chainDuration += boundaries[nextIdx + 1] - boundaries[nextIdx] - duration;
|
|
542
534
|
const xfadeType = trans.type === "fade" ? "fadeblack" : "fade";
|
|
543
535
|
const outV = i === sorted.length - 1 ? "outv" : `xv${i}`;
|
|
544
536
|
filterParts.push(
|
|
@@ -610,7 +602,7 @@ import { existsSync } from "fs";
|
|
|
610
602
|
import { readFile, unlink, writeFile } from "fs/promises";
|
|
611
603
|
import { isAbsolute, join, resolve } from "path";
|
|
612
604
|
function toWSLPathForTTS(path) {
|
|
613
|
-
return
|
|
605
|
+
return windowsDrivePathToWsl(path) ?? path;
|
|
614
606
|
}
|
|
615
607
|
function estimateActionDuration(step) {
|
|
616
608
|
if (step.action === "drag" && step.value === "around") {
|
|
@@ -677,7 +669,7 @@ async function saveVoiceoverMeta(outputDir, meta) {
|
|
|
677
669
|
async function concatenateAudio(inputs, output, workDir) {
|
|
678
670
|
const formatPath = (f) => {
|
|
679
671
|
const resolved = resolve(f);
|
|
680
|
-
return isWindows ?
|
|
672
|
+
return isWindows ? windowsDrivePathToWsl(resolved) ?? resolved : resolved;
|
|
681
673
|
};
|
|
682
674
|
const listContent = inputs.map((f) => `file '${formatPath(f)}'`).join("\n");
|
|
683
675
|
const listPath = join(workDir, "concat.txt");
|
|
@@ -786,23 +778,30 @@ init_utils();
|
|
|
786
778
|
var SPARK_AI_BASE = "https://sparkbrain.app";
|
|
787
779
|
var SPARK_AI_CHAT = `${SPARK_AI_BASE}/api/chat`;
|
|
788
780
|
var SPARK_AI_TOKEN = `${SPARK_AI_BASE}/api/models/token`;
|
|
781
|
+
var RELAY_BASE = process.env.QUICKPEEK_API_BASE || "https://quickpeek.co";
|
|
782
|
+
var RELAY_TOKEN_URL = `${RELAY_BASE}/api/cli/token`;
|
|
783
|
+
var RELAY_CHAT_URL = `${RELAY_BASE}/api/cli/ai`;
|
|
789
784
|
var API_KEY = process.env.SPARK_AI_KEY || "";
|
|
790
|
-
var
|
|
791
|
-
|
|
785
|
+
var cachedDirectToken = null;
|
|
786
|
+
var cachedRelayToken = null;
|
|
787
|
+
async function getOrRefreshDirectToken() {
|
|
792
788
|
const now = Date.now();
|
|
793
|
-
if (
|
|
794
|
-
return
|
|
789
|
+
if (cachedDirectToken && cachedDirectToken.expiresAt > now + 3e5) {
|
|
790
|
+
return cachedDirectToken.token;
|
|
795
791
|
}
|
|
796
792
|
try {
|
|
797
793
|
const response = await fetch(SPARK_AI_TOKEN, {
|
|
798
794
|
method: "POST",
|
|
799
795
|
headers: { "X-Api-Key": API_KEY }
|
|
800
796
|
});
|
|
797
|
+
if (!response.ok) {
|
|
798
|
+
throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
|
|
799
|
+
}
|
|
801
800
|
const data = await response.json();
|
|
802
|
-
if (!
|
|
803
|
-
throw new Error(data.error || `Token exchange failed:
|
|
801
|
+
if (!data.success || !data.token) {
|
|
802
|
+
throw new Error(data.error || `Token exchange failed: unexpected response`);
|
|
804
803
|
}
|
|
805
|
-
|
|
804
|
+
cachedDirectToken = {
|
|
806
805
|
token: data.token,
|
|
807
806
|
expiresAt: data.expiresAt || now + 36e5
|
|
808
807
|
};
|
|
@@ -811,77 +810,130 @@ async function getOrRefreshToken() {
|
|
|
811
810
|
throw new Error(`Failed to get Spark AI token: ${getErrorMessage(err)}`);
|
|
812
811
|
}
|
|
813
812
|
}
|
|
814
|
-
async function
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
error: "AI service is not configured. Please report this issue.",
|
|
819
|
-
isAuthError: true
|
|
820
|
-
};
|
|
813
|
+
async function getOrRefreshRelayToken(email) {
|
|
814
|
+
const now = Date.now();
|
|
815
|
+
if (cachedRelayToken && cachedRelayToken.email === email && cachedRelayToken.expiresAt > now + 3e5) {
|
|
816
|
+
return cachedRelayToken.token;
|
|
821
817
|
}
|
|
822
818
|
try {
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
819
|
+
const response = await fetch(RELAY_TOKEN_URL, {
|
|
820
|
+
method: "POST",
|
|
821
|
+
headers: { "Content-Type": "application/json" },
|
|
822
|
+
body: JSON.stringify({ email })
|
|
823
|
+
});
|
|
824
|
+
const data = await response.json().catch(() => ({}));
|
|
825
|
+
if (!response.ok || !data.success || !data.token) {
|
|
826
|
+
throw new Error(data.error || `Token request failed: ${response.status} ${response.statusText}`);
|
|
827
|
+
}
|
|
828
|
+
cachedRelayToken = {
|
|
829
|
+
token: data.token,
|
|
830
|
+
expiresAt: data.expiresAt || now + 36e5,
|
|
831
|
+
email
|
|
832
|
+
};
|
|
833
|
+
return data.token;
|
|
834
|
+
} catch (err) {
|
|
835
|
+
throw new Error(`Failed to get QuickPeek token: ${getErrorMessage(err)}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async function executeChat(chatUrl, bearerToken, systemPrompt, userPrompt, tier) {
|
|
839
|
+
let lastRateLimitError = null;
|
|
840
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
841
|
+
try {
|
|
842
|
+
const response = await fetch(chatUrl, {
|
|
843
|
+
method: "POST",
|
|
844
|
+
headers: {
|
|
845
|
+
"Content-Type": "application/json",
|
|
846
|
+
Authorization: `Bearer ${bearerToken}`
|
|
847
|
+
},
|
|
848
|
+
body: JSON.stringify({
|
|
849
|
+
messages: [
|
|
850
|
+
{ role: "system", content: systemPrompt },
|
|
851
|
+
{ role: "user", content: userPrompt }
|
|
852
|
+
]
|
|
853
|
+
})
|
|
854
|
+
});
|
|
855
|
+
let data = {};
|
|
856
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
857
|
+
if (contentType.includes("application/json")) {
|
|
858
|
+
data = await response.json();
|
|
859
|
+
}
|
|
860
|
+
if (!response.ok) {
|
|
861
|
+
const error = data.error || `Spark AI error: ${response.status} ${response.statusText}`;
|
|
862
|
+
if (response.status === 429 && attempt < 3) {
|
|
863
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
864
|
+
const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 1e3 * 2 ** (attempt - 1);
|
|
865
|
+
lastRateLimitError = {
|
|
856
866
|
success: false,
|
|
857
867
|
error,
|
|
858
|
-
isRateLimit:
|
|
859
|
-
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
if (!data.success || !data.response) {
|
|
863
|
-
return {
|
|
864
|
-
success: false,
|
|
865
|
-
error: data.error || "No response from Spark AI"
|
|
868
|
+
isRateLimit: true,
|
|
869
|
+
...retryAfter ? { retryAfterSeconds: parseInt(retryAfter, 10) } : {}
|
|
866
870
|
};
|
|
871
|
+
await new Promise((resolve3) => setTimeout(resolve3, Math.min(delayMs, 3e4)));
|
|
872
|
+
continue;
|
|
867
873
|
}
|
|
868
874
|
return {
|
|
869
|
-
success:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
limit: data.limits?.limit ?? 100,
|
|
874
|
-
tier,
|
|
875
|
-
...data.limits?.resetAt ? { resetAt: data.limits.resetAt } : {}
|
|
876
|
-
}
|
|
875
|
+
success: false,
|
|
876
|
+
error,
|
|
877
|
+
isRateLimit: response.status === 429,
|
|
878
|
+
isAuthError: response.status === 401 || response.status === 403
|
|
877
879
|
};
|
|
878
|
-
} catch (err) {
|
|
879
|
-
if (attempt === 3) throw err;
|
|
880
|
-
const delayMs = 1e3 * Math.pow(2, attempt - 1);
|
|
881
|
-
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
882
880
|
}
|
|
881
|
+
if (!data.success || !data.response) {
|
|
882
|
+
return {
|
|
883
|
+
success: false,
|
|
884
|
+
error: data.error || "No response from Spark AI"
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
success: true,
|
|
889
|
+
content: data.response,
|
|
890
|
+
limits: {
|
|
891
|
+
used: data.limits?.used ?? 0,
|
|
892
|
+
limit: data.limits?.limit ?? 100,
|
|
893
|
+
tier,
|
|
894
|
+
...data.limits?.resetAt ? { resetAt: data.limits.resetAt } : {}
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
} catch (err) {
|
|
898
|
+
if (attempt === 3) throw err;
|
|
899
|
+
const delayMs = 1e3 * 2 ** (attempt - 1);
|
|
900
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return lastRateLimitError || { success: false, error: "Request failed after retries" };
|
|
904
|
+
}
|
|
905
|
+
async function callAI(systemPrompt, userPrompt, tier = "free") {
|
|
906
|
+
if (!API_KEY) {
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
error: "AI service is not configured. Please report this issue.",
|
|
910
|
+
isAuthError: true
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const token = await getOrRefreshDirectToken();
|
|
915
|
+
return await executeChat(SPARK_AI_CHAT, token, systemPrompt, userPrompt, tier);
|
|
916
|
+
} catch (err) {
|
|
917
|
+
return { success: false, error: `Spark AI error: ${getErrorMessage(err)}` };
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async function callAIViaRelay(systemPrompt, userPrompt, email, tier = "free") {
|
|
921
|
+
if (!email) {
|
|
922
|
+
return {
|
|
923
|
+
success: false,
|
|
924
|
+
error: "Sign in required. Run qp login to connect your account.",
|
|
925
|
+
isAuthError: true
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
try {
|
|
929
|
+
let token = await getOrRefreshRelayToken(email);
|
|
930
|
+
let result = await executeChat(RELAY_CHAT_URL, token, systemPrompt, userPrompt, tier);
|
|
931
|
+
if (!result.success && result.isAuthError) {
|
|
932
|
+
cachedRelayToken = null;
|
|
933
|
+
token = await getOrRefreshRelayToken(email);
|
|
934
|
+
result = await executeChat(RELAY_CHAT_URL, token, systemPrompt, userPrompt, tier);
|
|
883
935
|
}
|
|
884
|
-
return
|
|
936
|
+
return result;
|
|
885
937
|
} catch (err) {
|
|
886
938
|
return { success: false, error: `Spark AI error: ${getErrorMessage(err)}` };
|
|
887
939
|
}
|
|
@@ -916,26 +968,32 @@ function normalizeUrl(input) {
|
|
|
916
968
|
var APP_ID = process.env.SPARK_APP_ID ?? "quickpeek";
|
|
917
969
|
var BASE = "https://sparkstripe.com";
|
|
918
970
|
var TIERS = {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
971
|
+
price_1TADnPEf9opkuWid9NtqOYON: "pro",
|
|
972
|
+
price_1TADnPEf9opkuWidgbvvX9ma: "pro",
|
|
973
|
+
price_1TADm7Ef9opkuWid3I9tquIB: "pro",
|
|
974
|
+
price_1TADm8Ef9opkuWid1RSA2wCa: "pro",
|
|
975
|
+
price_1TCREmEf9opkuWidvhuqzqDM: "pro",
|
|
976
|
+
price_1TCREmEf9opkuWidacf1tiC7: "pro"
|
|
925
977
|
};
|
|
926
978
|
var RANK = {
|
|
927
|
-
|
|
928
|
-
|
|
979
|
+
free: 0,
|
|
980
|
+
pro: 1
|
|
981
|
+
};
|
|
982
|
+
var FEATURES = {
|
|
983
|
+
free: [],
|
|
984
|
+
pro: [
|
|
985
|
+
"Everything in Free",
|
|
986
|
+
"No watermark",
|
|
987
|
+
"Full video composition (voiceover + captions + music + transitions)",
|
|
988
|
+
"Unlimited videos",
|
|
989
|
+
"Priority support"
|
|
990
|
+
]
|
|
929
991
|
};
|
|
930
|
-
var FEATURES = { "free": [], "pro": [
|
|
931
|
-
"Everything in Free",
|
|
932
|
-
"No watermark",
|
|
933
|
-
"Full video composition (voiceover + captions + music + transitions)",
|
|
934
|
-
"Unlimited videos",
|
|
935
|
-
"Priority support"
|
|
936
|
-
] };
|
|
937
992
|
async function _fetch(email, init) {
|
|
938
|
-
const r = await fetch(
|
|
993
|
+
const r = await fetch(
|
|
994
|
+
`${BASE}/api/public/subscription/status-public?email=${encodeURIComponent(email)}&app_id=${APP_ID}`,
|
|
995
|
+
init
|
|
996
|
+
);
|
|
939
997
|
if (!r.ok) throw new Error(`spark-stripe: ${r.status}`);
|
|
940
998
|
return r.json();
|
|
941
999
|
}
|
|
@@ -1152,11 +1210,20 @@ function getActionForType(type) {
|
|
|
1152
1210
|
}
|
|
1153
1211
|
var noopTask = () => ({ succeed: () => {
|
|
1154
1212
|
} });
|
|
1155
|
-
|
|
1213
|
+
var CRAWL_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1214
|
+
var CRAWL_CACHE_MAX_ENTRIES = 50;
|
|
1215
|
+
var crawlCache = /* @__PURE__ */ new Map();
|
|
1216
|
+
async function crawlPage(url, task = noopTask, storageStatePath) {
|
|
1217
|
+
const cached = crawlCache.get(url);
|
|
1218
|
+
if (cached && Date.now() - cached.timestamp < CRAWL_CACHE_TTL_MS) {
|
|
1219
|
+
task("Using cached crawl").succeed(`Found ${cached.elements.length} interactive elements (cached)`);
|
|
1220
|
+
return { title: cached.title, elements: [...cached.elements] };
|
|
1221
|
+
}
|
|
1156
1222
|
const browserTask = task("Launching browser");
|
|
1157
1223
|
const browser = await chromium.launch({ headless: true });
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1224
|
+
const context = await browser.newContext(storageStatePath ? { storageState: storageStatePath } : {});
|
|
1225
|
+
const page = await context.newPage();
|
|
1226
|
+
browserTask.succeed(storageStatePath ? "Browser launched (signed in)" : "Browser launched");
|
|
1160
1227
|
const crawlTask = task("Crawling page");
|
|
1161
1228
|
await page.goto(url, { waitUntil: "networkidle", timeout: 3e4 });
|
|
1162
1229
|
const title = await page.title();
|
|
@@ -1289,6 +1356,11 @@ async function crawlPage(url, task = noopTask) {
|
|
|
1289
1356
|
});
|
|
1290
1357
|
await browser.close();
|
|
1291
1358
|
crawlTask.succeed(`Found ${elements.length} interactive elements`);
|
|
1359
|
+
crawlCache.set(url, { timestamp: Date.now(), title, elements: [...elements] });
|
|
1360
|
+
if (crawlCache.size > CRAWL_CACHE_MAX_ENTRIES) {
|
|
1361
|
+
const oldest = crawlCache.keys().next().value;
|
|
1362
|
+
if (oldest !== void 0) crawlCache.delete(oldest);
|
|
1363
|
+
}
|
|
1292
1364
|
return { title, elements };
|
|
1293
1365
|
}
|
|
1294
1366
|
|
|
@@ -1426,6 +1498,7 @@ TARGET SYNTAX:
|
|
|
1426
1498
|
}
|
|
1427
1499
|
|
|
1428
1500
|
// src/lib/styles.ts
|
|
1501
|
+
import { buildAssHeader } from "@spark-apps/video-kit";
|
|
1429
1502
|
var CURSOR_MOVE_DURATION = 800;
|
|
1430
1503
|
var CURSOR_CSS = `
|
|
1431
1504
|
#qp-cursor {
|
|
@@ -1465,19 +1538,14 @@ var CURSOR_SCRIPT = `
|
|
|
1465
1538
|
})();
|
|
1466
1539
|
`;
|
|
1467
1540
|
function generateASSHeader(width, height, font, size, primaryColor, secondaryColor, outlineColor, backColor, bold, outlineWidth, shadow, marginBottom) {
|
|
1468
|
-
return
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
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
|
|
1477
|
-
|
|
1478
|
-
[Events]
|
|
1479
|
-
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
1480
|
-
`;
|
|
1541
|
+
return buildAssHeader({
|
|
1542
|
+
title: "QuickPeek Demo",
|
|
1543
|
+
width,
|
|
1544
|
+
height,
|
|
1545
|
+
styles: [
|
|
1546
|
+
`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`
|
|
1547
|
+
]
|
|
1548
|
+
});
|
|
1481
1549
|
}
|
|
1482
1550
|
|
|
1483
1551
|
// src/lib/subscription-client.ts
|
|
@@ -1560,75 +1628,7 @@ import { mkdir } from "fs/promises";
|
|
|
1560
1628
|
import { join as join5 } from "path";
|
|
1561
1629
|
|
|
1562
1630
|
// src/lib/voices.ts
|
|
1563
|
-
|
|
1564
|
-
en: "en-GB-SoniaNeural",
|
|
1565
|
-
ar: "ar-LB-LaylaNeural",
|
|
1566
|
-
es: "es-ES-ElviraNeural",
|
|
1567
|
-
fr: "fr-FR-DeniseNeural",
|
|
1568
|
-
de: "de-DE-KatjaNeural",
|
|
1569
|
-
it: "it-IT-ElsaNeural",
|
|
1570
|
-
pt: "pt-BR-FranciscaNeural",
|
|
1571
|
-
ru: "ru-RU-SvetlanaNeural",
|
|
1572
|
-
zh: "zh-CN-XiaoxiaoNeural",
|
|
1573
|
-
ja: "ja-JP-NanamiNeural",
|
|
1574
|
-
ko: "ko-KR-SunHiNeural",
|
|
1575
|
-
hi: "hi-IN-SwaraNeural",
|
|
1576
|
-
tr: "tr-TR-EmelNeural",
|
|
1577
|
-
nl: "nl-NL-ColetteNeural",
|
|
1578
|
-
pl: "pl-PL-AgnieszkaNeural",
|
|
1579
|
-
sv: "sv-SE-SofieNeural",
|
|
1580
|
-
he: "he-IL-HilaNeural",
|
|
1581
|
-
th: "th-TH-PremwadeeNeural",
|
|
1582
|
-
vi: "vi-VN-HoaiMyNeural",
|
|
1583
|
-
id: "id-ID-GadisNeural",
|
|
1584
|
-
ms: "ms-MY-YasminNeural",
|
|
1585
|
-
fil: "fil-PH-BlessicaNeural",
|
|
1586
|
-
uk: "uk-UA-PolinaNeural",
|
|
1587
|
-
cs: "cs-CZ-VlastaNeural",
|
|
1588
|
-
el: "el-GR-AthinaNeural",
|
|
1589
|
-
ro: "ro-RO-AlinaNeural",
|
|
1590
|
-
hu: "hu-HU-NoemiNeural",
|
|
1591
|
-
da: "da-DK-ChristelNeural",
|
|
1592
|
-
fi: "fi-FI-NooraNeural",
|
|
1593
|
-
no: "nb-NO-PernilleNeural",
|
|
1594
|
-
sk: "sk-SK-ViktoriaNeural",
|
|
1595
|
-
bg: "bg-BG-KalinaNeural",
|
|
1596
|
-
hr: "hr-HR-GabrijelaNeural",
|
|
1597
|
-
sl: "sl-SI-PetraNeural",
|
|
1598
|
-
et: "et-EE-AnuNeural",
|
|
1599
|
-
lv: "lv-LV-EveritaNeural",
|
|
1600
|
-
lt: "lt-LT-OnaNeural",
|
|
1601
|
-
bn: "bn-IN-TanishaaNeural",
|
|
1602
|
-
ta: "ta-IN-PallaviNeural",
|
|
1603
|
-
te: "te-IN-ShrutiNeural",
|
|
1604
|
-
mr: "mr-IN-AarohiNeural",
|
|
1605
|
-
gu: "gu-IN-DhwaniNeural",
|
|
1606
|
-
kn: "kn-IN-SapnaNeural",
|
|
1607
|
-
ml: "ml-IN-SobhanaNeural",
|
|
1608
|
-
ur: "ur-PK-UzmaNeural",
|
|
1609
|
-
fa: "fa-IR-DilaraNeural",
|
|
1610
|
-
sw: "sw-KE-ZuriNeural",
|
|
1611
|
-
af: "af-ZA-AdriNeural",
|
|
1612
|
-
ca: "ca-ES-JoanaNeural",
|
|
1613
|
-
cy: "cy-GB-NiaNeural",
|
|
1614
|
-
ga: "ga-IE-OrlaNeural",
|
|
1615
|
-
mt: "mt-MT-GraceNeural",
|
|
1616
|
-
is: "is-IS-GudrunNeural",
|
|
1617
|
-
mk: "mk-MK-MarijaNeural",
|
|
1618
|
-
sr: "sr-RS-SophieNeural",
|
|
1619
|
-
bs: "bs-BA-VesnaNeural",
|
|
1620
|
-
sq: "sq-AL-AnilaNeural",
|
|
1621
|
-
am: "am-ET-MekdesNeural",
|
|
1622
|
-
az: "az-AZ-BabekNeural",
|
|
1623
|
-
ka: "ka-GE-EkaNeural",
|
|
1624
|
-
kk: "kk-KZ-AigulNeural",
|
|
1625
|
-
mn: "mn-MN-YesUINeural",
|
|
1626
|
-
ne: "ne-NP-HemkalaNeural",
|
|
1627
|
-
ps: "ps-AF-LatifaNeural",
|
|
1628
|
-
si: "si-LK-ThiliniNeural",
|
|
1629
|
-
so: "so-SO-UbaxNeural",
|
|
1630
|
-
zu: "zu-ZA-ThandoNeural"
|
|
1631
|
-
};
|
|
1631
|
+
import { WIDE_VOICES } from "@spark-apps/video-kit";
|
|
1632
1632
|
|
|
1633
1633
|
// src/lib/tts/generate.ts
|
|
1634
1634
|
init_helpers();
|
|
@@ -1695,7 +1695,7 @@ function estimateDurationFromText(text, wordsPerMinute = 150) {
|
|
|
1695
1695
|
}
|
|
1696
1696
|
function resolveVoice(config) {
|
|
1697
1697
|
if (config.audio?.voice) return config.audio.voice;
|
|
1698
|
-
if (config.lang) return
|
|
1698
|
+
if (config.lang) return WIDE_VOICES[config.lang] || null;
|
|
1699
1699
|
return null;
|
|
1700
1700
|
}
|
|
1701
1701
|
async function generateVoiceover(steps, config, workDir, outputDir, logError, task, info) {
|
|
@@ -1803,7 +1803,7 @@ export {
|
|
|
1803
1803
|
EXCLUDED_LINK_PATTERNS,
|
|
1804
1804
|
FEATURES,
|
|
1805
1805
|
INTERACTIVE_SELECTORS,
|
|
1806
|
-
LANGUAGE_VOICES,
|
|
1806
|
+
WIDE_VOICES as LANGUAGE_VOICES,
|
|
1807
1807
|
VERSION,
|
|
1808
1808
|
alignVoiceoverToRecording,
|
|
1809
1809
|
applyTransitions,
|
|
@@ -1813,6 +1813,7 @@ export {
|
|
|
1813
1813
|
buildUserPrompt,
|
|
1814
1814
|
buildWatermarkFilters,
|
|
1815
1815
|
callAI,
|
|
1816
|
+
callAIViaRelay,
|
|
1816
1817
|
canReuseVoiceover,
|
|
1817
1818
|
composeVideo,
|
|
1818
1819
|
concatMedia,
|
|
@@ -1831,7 +1832,7 @@ export {
|
|
|
1831
1832
|
getUpgradeUrl,
|
|
1832
1833
|
hasAccess,
|
|
1833
1834
|
hasTrialRemaining,
|
|
1834
|
-
hexToASS,
|
|
1835
|
+
hexToAss as hexToASS,
|
|
1835
1836
|
isCanceling,
|
|
1836
1837
|
isExcludedLink,
|
|
1837
1838
|
isPaid,
|
|
@@ -1853,5 +1854,5 @@ export {
|
|
|
1853
1854
|
spliceVideos,
|
|
1854
1855
|
synthesizeSpeech,
|
|
1855
1856
|
toTitleCase,
|
|
1856
|
-
windowsPathToWSL
|
|
1857
|
+
windowsDrivePathToWsl as windowsPathToWSL
|
|
1857
1858
|
};
|