vidspotai-shared 1.0.99 → 1.0.101-dev.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/lib/globals/aiModels/enums.d.ts +1 -0
- package/lib/globals/aiModels/enums.d.ts.map +1 -1
- package/lib/globals/aiModels/enums.js +3 -0
- package/lib/globals/aiModels/extendCapability.d.ts +26 -0
- package/lib/globals/aiModels/extendCapability.d.ts.map +1 -0
- package/lib/globals/aiModels/extendCapability.js +42 -0
- package/lib/globals/aiModels/index.d.ts +1 -0
- package/lib/globals/aiModels/index.d.ts.map +1 -1
- package/lib/globals/aiModels/index.js +1 -0
- package/lib/globals/aiModels/providers/hostedAvatars.js +10 -10
- package/lib/globals/creditUnit.d.ts +20 -33
- package/lib/globals/creditUnit.d.ts.map +1 -1
- package/lib/globals/creditUnit.js +20 -33
- package/lib/globals/types.d.ts +34 -2
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +37 -0
- package/lib/models/demoVideoProject.model.d.ts +213 -0
- package/lib/models/demoVideoProject.model.d.ts.map +1 -1
- package/lib/models/index.d.ts +2 -0
- package/lib/models/index.d.ts.map +1 -1
- package/lib/models/index.js +2 -0
- package/lib/models/mediaUpload.model.d.ts +25 -0
- package/lib/models/mediaUpload.model.d.ts.map +1 -1
- package/lib/models/productConnection.model.d.ts +57 -0
- package/lib/models/productConnection.model.d.ts.map +1 -0
- package/lib/models/productConnection.model.js +7 -0
- package/lib/models/video.model.d.ts +11 -0
- package/lib/models/video.model.d.ts.map +1 -1
- package/lib/models/videoQaJob.model.d.ts +61 -0
- package/lib/models/videoQaJob.model.d.ts.map +1 -0
- package/lib/models/videoQaJob.model.js +16 -0
- package/lib/schemas/brandKit.schema.d.ts +3 -1
- package/lib/schemas/brandKit.schema.d.ts.map +1 -1
- package/lib/schemas/brandKit.schema.js +9 -0
- package/lib/schemas/brief.schema.d.ts +5 -4
- package/lib/schemas/brief.schema.d.ts.map +1 -1
- package/lib/schemas/brief.schema.js +23 -1
- package/lib/schemas/demoQuestion.schema.d.ts +24 -4
- package/lib/schemas/demoQuestion.schema.d.ts.map +1 -1
- package/lib/schemas/demoQuestion.schema.js +8 -2
- package/lib/schemas/demoSpec.schema.d.ts +40 -6
- package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
- package/lib/schemas/demoSpec.schema.js +46 -1
- package/lib/schemas/project.schema.d.ts +33 -3
- package/lib/schemas/project.schema.d.ts.map +1 -1
- package/lib/schemas/project.schema.js +22 -0
- package/lib/schemas/social.schema.d.ts +1 -1
- package/lib/schemas/videoPlan.schema.d.ts +22 -20
- package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
- package/lib/schemas/videoPlan.schema.js +19 -0
- package/lib/services/agent/editClassifier.d.ts +10 -10
- package/lib/services/agent/eval/types.d.ts +3 -2
- package/lib/services/agent/eval/types.d.ts.map +1 -1
- package/lib/services/agent/executor/core.d.ts.map +1 -1
- package/lib/services/agent/executor/core.js +11 -0
- package/lib/services/agent/planner/Planner.d.ts.map +1 -1
- package/lib/services/agent/planner/Planner.js +5 -0
- package/lib/services/agent/planner/overlayRegen.d.ts.map +1 -1
- package/lib/services/agent/planner/overlayRegen.js +5 -0
- package/lib/services/agent/planner/plannerMessages.d.ts.map +1 -1
- package/lib/services/agent/planner/plannerMessages.js +10 -1
- package/lib/services/agent/planner/scriptFirstPlanner.d.ts.map +1 -1
- package/lib/services/agent/planner/scriptFirstPlanner.js +2 -0
- package/lib/services/agent/regenCore.d.ts.map +1 -1
- package/lib/services/agent/regenCore.js +11 -1
- package/lib/services/agent/runHelpers.d.ts +8 -0
- package/lib/services/agent/runHelpers.d.ts.map +1 -1
- package/lib/services/agent/runHelpers.js +5 -1
- package/lib/services/agent/tools/chapterOutline.tool.d.ts +8 -0
- package/lib/services/agent/tools/chapterOutline.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/chapterOutline.tool.js +21 -8
- package/lib/services/agent/tools/composeScene.tool.d.ts +32 -2
- package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts +11 -1
- package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/extendVideo.tool.d.ts +9 -3
- package/lib/services/agent/tools/extendVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/extendVideo.tool.js +12 -27
- package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +1 -1
- package/lib/services/agent/tools/generateScript.tool.d.ts +3 -2
- package/lib/services/agent/tools/generateScript.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/generateScript.tool.js +16 -7
- package/lib/services/agent/tools/generateVoiceover.tool.d.ts +1 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts +11 -8
- package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/render.tool.d.ts +11 -1
- package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
- package/lib/services/agent/tools/searchStock.tool.d.ts +1 -1
- package/lib/services/agent/tools/searchUserLibrary.tool.d.ts +4 -4
- package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/kling.service.js +22 -3
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts +7 -0
- package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
- package/lib/services/aiGen/providers/kling/klingStatus.js +41 -4
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +25 -0
- package/lib/services/aiGen/providers/types.d.ts +20 -0
- package/lib/services/aiGen/providers/types.d.ts.map +1 -1
- package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -1
- package/lib/services/avatarGen/avatarProviderFactory.js +3 -0
- package/lib/services/avatarGen/index.d.ts +1 -0
- package/lib/services/avatarGen/index.d.ts.map +1 -1
- package/lib/services/avatarGen/index.js +1 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts +53 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts.map +1 -0
- package/lib/services/avatarGen/providers/synthesiaAvatarProvider.js +176 -0
- package/lib/services/demo/demoAuthor.d.ts +7 -0
- package/lib/services/demo/demoAuthor.d.ts.map +1 -1
- package/lib/services/demo/demoAuthor.js +2 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts +6 -0
- package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -1
- package/lib/services/demo/demoAuthorPrompt.js +24 -2
- package/lib/services/demo/demoCaptions.d.ts +69 -0
- package/lib/services/demo/demoCaptions.d.ts.map +1 -0
- package/lib/services/demo/demoCaptions.js +152 -0
- package/lib/services/demo/demoMusic.d.ts +8 -1
- package/lib/services/demo/demoMusic.d.ts.map +1 -1
- package/lib/services/demo/demoMusic.js +32 -16
- package/lib/services/demo/demoScriptPrompt.d.ts +2 -1
- package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -1
- package/lib/services/demo/demoScriptPrompt.js +29 -2
- package/lib/services/demo/demoStockAssets.d.ts +29 -0
- package/lib/services/demo/demoStockAssets.d.ts.map +1 -0
- package/lib/services/demo/demoStockAssets.js +107 -0
- package/lib/services/demo/index.d.ts +3 -0
- package/lib/services/demo/index.d.ts.map +1 -1
- package/lib/services/demo/index.js +3 -0
- package/lib/services/demo/productConnection.service.d.ts +78 -0
- package/lib/services/demo/productConnection.service.d.ts.map +1 -0
- package/lib/services/demo/productConnection.service.js +125 -0
- package/lib/services/editor/planToProject.d.ts.map +1 -1
- package/lib/services/editor/planToProject.helpers.d.ts +1 -0
- package/lib/services/editor/planToProject.helpers.d.ts.map +1 -1
- package/lib/services/editor/planToProject.helpers.js +3 -0
- package/lib/services/editor/planToProject.js +39 -0
- package/lib/services/editor/projectToDesign.js +13 -1
- package/lib/services/firestore.service.d.ts +14 -0
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +16 -0
- package/lib/services/translation/index.d.ts +1 -0
- package/lib/services/translation/index.d.ts.map +1 -1
- package/lib/services/translation/index.js +8 -1
- package/lib/services/translation/outputLanguage.d.ts +75 -0
- package/lib/services/translation/outputLanguage.d.ts.map +1 -0
- package/lib/services/translation/outputLanguage.js +190 -0
- package/lib/services/translation/translation.service.d.ts +13 -2
- package/lib/services/translation/translation.service.d.ts.map +1 -1
- package/lib/services/translation/translation.service.js +66 -5
- package/lib/services/tts/index.d.ts +1 -0
- package/lib/services/tts/index.d.ts.map +1 -1
- package/lib/services/tts/index.js +1 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts +7 -0
- package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
- package/lib/services/tts/providers/elevenlabs.service.js +69 -13
- package/lib/services/tts/types.d.ts +29 -0
- package/lib/services/tts/types.d.ts.map +1 -1
- package/lib/services/tts/wordTimings.d.ts +27 -0
- package/lib/services/tts/wordTimings.d.ts.map +1 -0
- package/lib/services/tts/wordTimings.js +57 -0
- package/lib/utils/billableDuration.d.ts +11 -0
- package/lib/utils/billableDuration.d.ts.map +1 -0
- package/lib/utils/billableDuration.js +91 -0
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/index.js +3 -0
- package/lib/utils/mp4Duration.d.ts +38 -0
- package/lib/utils/mp4Duration.d.ts.map +1 -0
- package/lib/utils/mp4Duration.js +164 -0
- package/lib/utils/socialEntitlements.d.ts +51 -0
- package/lib/utils/socialEntitlements.d.ts.map +1 -0
- package/lib/utils/socialEntitlements.js +65 -0
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { IDemoCaptionTrack, IDemoCaptionWord } from "../../models/demoVideoProject.model";
|
|
2
|
+
import { DemoSpec } from "../../schemas/demoSpec.schema";
|
|
3
|
+
/**
|
|
4
|
+
* Caption track construction for the SaaS Demo Video pipeline (audit finding G3).
|
|
5
|
+
*
|
|
6
|
+
* Social feeds autoplay MUTED, so a demo whose whole argument lives in the
|
|
7
|
+
* voiceover says nothing there. This module turns the synthesized narration into
|
|
8
|
+
* readable, word-timed cues that the render burns in, plus an `.srt` sidecar for
|
|
9
|
+
* platforms that accept an upload.
|
|
10
|
+
*
|
|
11
|
+
* The interesting problem is not timing, it's LINE LENGTH: a narration segment is
|
|
12
|
+
* per-scene and can be 30+ words. Drawn as one line that's an unreadable wall, so
|
|
13
|
+
* segments are split into cues sized to what a viewer takes in at a glance,
|
|
14
|
+
* breaking on sentence boundaries wherever the text offers one.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A segment of narration with its words already timed on the VIDEO timeline —
|
|
18
|
+
* i.e. the voicing stage has applied any `atempo` correction and added the
|
|
19
|
+
* segment's `startMs`. This module never sees provider-relative time.
|
|
20
|
+
*/
|
|
21
|
+
export interface DemoNarrationTiming {
|
|
22
|
+
/** Scene this narration belongs to (matches DemoScene.id). */
|
|
23
|
+
sceneId: string;
|
|
24
|
+
/** The spoken copy, verbatim — this is what gets displayed. */
|
|
25
|
+
text: string;
|
|
26
|
+
/** Absolute start on the video timeline, ms. */
|
|
27
|
+
startMs: number;
|
|
28
|
+
/** Absolute end on the video timeline, ms (post-compression, slot-clamped). */
|
|
29
|
+
endMs: number;
|
|
30
|
+
/**
|
|
31
|
+
* Absolute-timed words from the provider's character alignment. Empty when the
|
|
32
|
+
* provider returned none — this module then falls back to proportional timing.
|
|
33
|
+
*/
|
|
34
|
+
words: IDemoCaptionWord[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Should this video burn captions into the picture?
|
|
38
|
+
*
|
|
39
|
+
* Portrait and square ALWAYS: those aspects exist for social feeds, which autoplay
|
|
40
|
+
* muted, so an un-captioned vertical cut ships with its argument inaudible. That is
|
|
41
|
+
* exactly the G2/G3 "social does not ship" verdict.
|
|
42
|
+
*
|
|
43
|
+
* Landscape only when the style asked for a captions-led read (`audioIntent`).
|
|
44
|
+
* A 16:9 landing-page or YC cut is watched with sound, and permanent burned-in
|
|
45
|
+
* text there competes with the on-screen lower-thirds the planner already wrote.
|
|
46
|
+
*
|
|
47
|
+
* Pure by design — the `DEMO_CAPTIONS_DISABLED` kill switch is applied by the
|
|
48
|
+
* worker, so this stays trivially testable.
|
|
49
|
+
*/
|
|
50
|
+
export declare function shouldBurnCaptions(spec: Pick<DemoSpec, "aspect" | "style">): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Split timed narration into readable cues.
|
|
53
|
+
*
|
|
54
|
+
* `fromAlignment` records whether the times came from real provider alignment or
|
|
55
|
+
* from proportional estimation, so the QA/report layer can tell an honest
|
|
56
|
+
* karaoke-synced track from a best-effort one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildCaptionTrack(segments: DemoNarrationTiming[]): IDemoCaptionTrack;
|
|
59
|
+
/**
|
|
60
|
+
* Serialize a caption track as SubRip (`.srt`) — the format YouTube, LinkedIn and
|
|
61
|
+
* Vimeo all accept as an uploaded subtitle file. Shipping this alongside the
|
|
62
|
+
* burned-in captions also makes the narration searchable/indexable, which
|
|
63
|
+
* burned-in pixels never are.
|
|
64
|
+
*
|
|
65
|
+
* A zero-length cue is emitted with a minimum 1ms span rather than dropped: some
|
|
66
|
+
* parsers reject `start == end`, and losing a line is worse than a rounding nudge.
|
|
67
|
+
*/
|
|
68
|
+
export declare function captionTrackToSrt(track: IDemoCaptionTrack): string;
|
|
69
|
+
//# sourceMappingURL=demoCaptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoCaptions.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoCaptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAsBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,OAAO,CAGpF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,iBAAiB,CAuBpF;AAsED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAOlE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldBurnCaptions = shouldBurnCaptions;
|
|
4
|
+
exports.buildCaptionTrack = buildCaptionTrack;
|
|
5
|
+
exports.captionTrackToSrt = captionTrackToSrt;
|
|
6
|
+
/**
|
|
7
|
+
* Cue sizing. Tuned for a phone-held-vertically read: ~5–8 words is one glance,
|
|
8
|
+
* and a line that sits longer than ~2.8s feels stalled even when the speech
|
|
9
|
+
* behind it is still going.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_CUE_WORDS = 8;
|
|
12
|
+
const MAX_CUE_MS = 2800;
|
|
13
|
+
/**
|
|
14
|
+
* Don't honour a punctuation break below this many words, or "Yes. Really." emits
|
|
15
|
+
* two one-word flashes.
|
|
16
|
+
*/
|
|
17
|
+
const MIN_CUE_WORDS = 3;
|
|
18
|
+
/** A comma is a weaker break than a full stop — only take it once the line has body. */
|
|
19
|
+
const MIN_COMMA_BREAK_WORDS = 5;
|
|
20
|
+
/** Ends a sentence — the strongest place to cut a caption line. */
|
|
21
|
+
const SENTENCE_END = /[.!?:]["')\]]?$/;
|
|
22
|
+
/** A softer clause boundary. */
|
|
23
|
+
const CLAUSE_END = /[,;—–]["')\]]?$/;
|
|
24
|
+
/**
|
|
25
|
+
* Should this video burn captions into the picture?
|
|
26
|
+
*
|
|
27
|
+
* Portrait and square ALWAYS: those aspects exist for social feeds, which autoplay
|
|
28
|
+
* muted, so an un-captioned vertical cut ships with its argument inaudible. That is
|
|
29
|
+
* exactly the G2/G3 "social does not ship" verdict.
|
|
30
|
+
*
|
|
31
|
+
* Landscape only when the style asked for a captions-led read (`audioIntent`).
|
|
32
|
+
* A 16:9 landing-page or YC cut is watched with sound, and permanent burned-in
|
|
33
|
+
* text there competes with the on-screen lower-thirds the planner already wrote.
|
|
34
|
+
*
|
|
35
|
+
* Pure by design — the `DEMO_CAPTIONS_DISABLED` kill switch is applied by the
|
|
36
|
+
* worker, so this stays trivially testable.
|
|
37
|
+
*/
|
|
38
|
+
function shouldBurnCaptions(spec) {
|
|
39
|
+
if (spec.style.audioIntent === "music")
|
|
40
|
+
return false;
|
|
41
|
+
return spec.aspect === "16:9" ? spec.style.audioIntent === "captions" : true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Split timed narration into readable cues.
|
|
45
|
+
*
|
|
46
|
+
* `fromAlignment` records whether the times came from real provider alignment or
|
|
47
|
+
* from proportional estimation, so the QA/report layer can tell an honest
|
|
48
|
+
* karaoke-synced track from a best-effort one.
|
|
49
|
+
*/
|
|
50
|
+
function buildCaptionTrack(segments) {
|
|
51
|
+
const cues = [];
|
|
52
|
+
let anyAlignment = false;
|
|
53
|
+
let allAlignment = segments.length > 0;
|
|
54
|
+
for (const seg of segments) {
|
|
55
|
+
const words = seg.words.length ? seg.words : estimateWords(seg);
|
|
56
|
+
if (seg.words.length)
|
|
57
|
+
anyAlignment = true;
|
|
58
|
+
else
|
|
59
|
+
allAlignment = false;
|
|
60
|
+
if (!words.length)
|
|
61
|
+
continue;
|
|
62
|
+
cues.push(...cuesFromWords(seg, words, seg.words.length > 0));
|
|
63
|
+
}
|
|
64
|
+
// Never let a cue outlive the next one's start: a slot-clamped segment can end
|
|
65
|
+
// after the following segment begins, and two captions on screen at once reads
|
|
66
|
+
// as a bug even when the audio underneath is correct.
|
|
67
|
+
for (let i = 0; i < cues.length - 1; i++) {
|
|
68
|
+
const cur = cues[i];
|
|
69
|
+
const next = cues[i + 1];
|
|
70
|
+
if (cur.endMs > next.startMs)
|
|
71
|
+
cur.endMs = Math.max(cur.startMs, next.startMs);
|
|
72
|
+
}
|
|
73
|
+
return { cues, fromAlignment: anyAlignment && allAlignment };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Space a segment's words evenly across its spoken window. The honest fallback
|
|
77
|
+
* when a provider returned no alignment: line breaks and cue timing stay right
|
|
78
|
+
* (they're driven by the segment window, which we measured with ffprobe), only
|
|
79
|
+
* the within-line word emphasis becomes approximate.
|
|
80
|
+
*/
|
|
81
|
+
function estimateWords(seg) {
|
|
82
|
+
const tokens = seg.text.trim().split(/\s+/).filter(Boolean);
|
|
83
|
+
if (!tokens.length)
|
|
84
|
+
return [];
|
|
85
|
+
const span = Math.max(0, seg.endMs - seg.startMs);
|
|
86
|
+
const per = span / tokens.length;
|
|
87
|
+
return tokens.map((word, i) => ({
|
|
88
|
+
word,
|
|
89
|
+
startMs: Math.round(seg.startMs + per * i),
|
|
90
|
+
endMs: Math.round(seg.startMs + per * (i + 1)),
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
/** Greedy line-breaker: fill a cue until a good break appears or the bounds hit. */
|
|
94
|
+
function cuesFromWords(seg, words, real) {
|
|
95
|
+
const out = [];
|
|
96
|
+
let cur = [];
|
|
97
|
+
const flush = () => {
|
|
98
|
+
if (!cur.length)
|
|
99
|
+
return;
|
|
100
|
+
const first = cur[0];
|
|
101
|
+
const last = cur[cur.length - 1];
|
|
102
|
+
out.push({
|
|
103
|
+
sceneId: seg.sceneId,
|
|
104
|
+
text: cur.map((w) => w.word).join(" "),
|
|
105
|
+
startMs: first.startMs,
|
|
106
|
+
endMs: Math.max(last.endMs, first.startMs + 1),
|
|
107
|
+
// Only publish per-word timings we actually measured. Estimated words would
|
|
108
|
+
// drive the karaoke fill off the real speech, which looks worse than the
|
|
109
|
+
// renderer's own even spacing — and the renderer already does that for [].
|
|
110
|
+
words: real ? cur.map((w) => ({ ...w })) : [],
|
|
111
|
+
});
|
|
112
|
+
cur = [];
|
|
113
|
+
};
|
|
114
|
+
for (const w of words) {
|
|
115
|
+
cur.push(w);
|
|
116
|
+
const spanMs = w.endMs - cur[0].startMs;
|
|
117
|
+
const hitBound = cur.length >= MAX_CUE_WORDS || spanMs >= MAX_CUE_MS;
|
|
118
|
+
const sentence = cur.length >= MIN_CUE_WORDS && SENTENCE_END.test(w.word);
|
|
119
|
+
const clause = cur.length >= MIN_COMMA_BREAK_WORDS && CLAUSE_END.test(w.word);
|
|
120
|
+
if (hitBound || sentence || clause)
|
|
121
|
+
flush();
|
|
122
|
+
}
|
|
123
|
+
flush();
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/** `1234` → `00:00:01,234` (SRT timecode). */
|
|
127
|
+
function srtTime(ms) {
|
|
128
|
+
const total = Math.max(0, Math.round(ms));
|
|
129
|
+
const h = Math.floor(total / 3600000);
|
|
130
|
+
const m = Math.floor((total % 3600000) / 60000);
|
|
131
|
+
const s = Math.floor((total % 60000) / 1000);
|
|
132
|
+
const milli = total % 1000;
|
|
133
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
134
|
+
return `${pad(h)}:${pad(m)}:${pad(s)},${pad(milli, 3)}`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Serialize a caption track as SubRip (`.srt`) — the format YouTube, LinkedIn and
|
|
138
|
+
* Vimeo all accept as an uploaded subtitle file. Shipping this alongside the
|
|
139
|
+
* burned-in captions also makes the narration searchable/indexable, which
|
|
140
|
+
* burned-in pixels never are.
|
|
141
|
+
*
|
|
142
|
+
* A zero-length cue is emitted with a minimum 1ms span rather than dropped: some
|
|
143
|
+
* parsers reject `start == end`, and losing a line is worse than a rounding nudge.
|
|
144
|
+
*/
|
|
145
|
+
function captionTrackToSrt(track) {
|
|
146
|
+
return track.cues
|
|
147
|
+
.map((cue, i) => {
|
|
148
|
+
const end = Math.max(cue.endMs, cue.startMs + 1);
|
|
149
|
+
return `${i + 1}\n${srtTime(cue.startMs)} --> ${srtTime(end)}\n${cue.text}\n`;
|
|
150
|
+
})
|
|
151
|
+
.join("\n");
|
|
152
|
+
}
|
|
@@ -9,5 +9,12 @@ export interface DemoMusicSelection {
|
|
|
9
9
|
* Pick a reachable background-music bed for a demo. Returns null (never throws)
|
|
10
10
|
* when nothing is reachable — the caller ships the video without a bed.
|
|
11
11
|
*/
|
|
12
|
-
export declare function selectDemoMusic(spec: DemoSpec
|
|
12
|
+
export declare function selectDemoMusic(spec: DemoSpec, opts?: {
|
|
13
|
+
/**
|
|
14
|
+
* A user-uploaded music bed (from the "have a track?" asset ask). When set
|
|
15
|
+
* and reachable it WINS over the curated catalog — the user's own track,
|
|
16
|
+
* ducked under any voiceover in the mux exactly like a curated bed.
|
|
17
|
+
*/
|
|
18
|
+
preferredUrl?: string;
|
|
19
|
+
}): Promise<DemoMusicSelection | null>;
|
|
13
20
|
//# sourceMappingURL=demoMusic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demoMusic.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoMusic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAsBzD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"demoMusic.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoMusic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAsBzD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAyDD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;IACL;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA6BpC"}
|
|
@@ -46,23 +46,29 @@ function moodForStyle(spec) {
|
|
|
46
46
|
* a dead CDN edge silently ships no music rather than 404-crashing the mux.
|
|
47
47
|
* (Same guard shape as `selectMusicForPlan`'s `pickFirstReachable`.)
|
|
48
48
|
*/
|
|
49
|
+
async function urlReachable(url) {
|
|
50
|
+
try {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
53
|
+
const r = await fetch(url, { method: "HEAD", signal: controller.signal });
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
if (r.ok)
|
|
56
|
+
return true;
|
|
57
|
+
logger_1.logger.warn(`${TAG} track HEAD non-2xx, skipping`, { url, status: r.status });
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
logger_1.logger.warn(`${TAG} track HEAD failed, skipping`, {
|
|
62
|
+
url,
|
|
63
|
+
err: e instanceof Error ? e.message : String(e),
|
|
64
|
+
});
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
49
68
|
async function firstReachable(tracks) {
|
|
50
69
|
for (const t of tracks) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
54
|
-
const r = await fetch(t.url, { method: "HEAD", signal: controller.signal });
|
|
55
|
-
clearTimeout(timeout);
|
|
56
|
-
if (r.ok)
|
|
57
|
-
return t;
|
|
58
|
-
logger_1.logger.warn(`${TAG} track HEAD non-2xx, skipping`, { url: t.url, status: r.status });
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
logger_1.logger.warn(`${TAG} track HEAD failed, skipping`, {
|
|
62
|
-
url: t.url,
|
|
63
|
-
err: e instanceof Error ? e.message : String(e),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
70
|
+
if (await urlReachable(t.url))
|
|
71
|
+
return t;
|
|
66
72
|
}
|
|
67
73
|
return undefined;
|
|
68
74
|
}
|
|
@@ -70,7 +76,17 @@ async function firstReachable(tracks) {
|
|
|
70
76
|
* Pick a reachable background-music bed for a demo. Returns null (never throws)
|
|
71
77
|
* when nothing is reachable — the caller ships the video without a bed.
|
|
72
78
|
*/
|
|
73
|
-
async function selectDemoMusic(spec) {
|
|
79
|
+
async function selectDemoMusic(spec, opts) {
|
|
80
|
+
// User-provided track wins when reachable (still fail-soft to the catalog).
|
|
81
|
+
if (opts?.preferredUrl && (await urlReachable(opts.preferredUrl))) {
|
|
82
|
+
logger_1.logger.info(`${TAG} using user-provided bed`, { url: opts.preferredUrl });
|
|
83
|
+
return { url: opts.preferredUrl, attribution: "User-provided" };
|
|
84
|
+
}
|
|
85
|
+
if (opts?.preferredUrl) {
|
|
86
|
+
logger_1.logger.warn(`${TAG} user bed unreachable — falling back to catalog`, {
|
|
87
|
+
url: opts.preferredUrl,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
74
90
|
const { mood, query, bpmTarget } = moodForStyle(spec);
|
|
75
91
|
try {
|
|
76
92
|
// Don't pass durationSecMin — every curated track is looped to the video
|
|
@@ -36,7 +36,8 @@ export declare function wordCount(text: string): number;
|
|
|
36
36
|
* 1. keep as many WHOLE sentences as fit within budget;
|
|
37
37
|
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
38
38
|
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
39
|
-
* 3.
|
|
39
|
+
* 3. drop any dangling function-word tail so the line ends on a content word;
|
|
40
|
+
* 4. always end on terminal punctuation.
|
|
40
41
|
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
41
42
|
*/
|
|
42
43
|
export declare function fitNarrationToBudget(text: string, budget: number): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demoScriptPrompt.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoScriptPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAY3C,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;
|
|
1
|
+
{"version":3,"file":"demoScriptPrompt.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoScriptPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAY3C,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AA0DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA6BpC;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,oBAAoB,EAAE,CAU5E;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAkBpD;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,oBAAoB,EAAE,GAC7B,MAAM,CAuBR"}
|
|
@@ -72,6 +72,32 @@ function splitSentences(text) {
|
|
|
72
72
|
out.push(text.slice(last).trim());
|
|
73
73
|
return out.filter(Boolean);
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Words that must never END a trimmed line: cutting here yields a dangling
|
|
77
|
+
* fragment ("…describe what you want and.") that reads as broken copy and sounds
|
|
78
|
+
* worse spoken than a shorter, complete phrase. Conjunctions, prepositions,
|
|
79
|
+
* articles, and auxiliaries all imply a continuation that was cut away.
|
|
80
|
+
*/
|
|
81
|
+
const DANGLING_TAIL_WORDS = new Set([
|
|
82
|
+
"a", "an", "and", "as", "at", "but", "by", "for", "from", "if", "in", "into",
|
|
83
|
+
"is", "it", "its", "of", "on", "or", "so", "than", "that", "the", "then",
|
|
84
|
+
"to", "up", "was", "were", "when", "which", "while", "with", "you", "your",
|
|
85
|
+
]);
|
|
86
|
+
/**
|
|
87
|
+
* Drop trailing function words so a capped span ends on a content word. Bounded:
|
|
88
|
+
* never strips below half the words it was given, so a line made entirely of
|
|
89
|
+
* function words degrades to the plain cap rather than to nothing.
|
|
90
|
+
*/
|
|
91
|
+
function trimDanglingTail(text) {
|
|
92
|
+
const words = text.trim().split(/\s+/).filter(Boolean);
|
|
93
|
+
const floor = Math.ceil(words.length / 2);
|
|
94
|
+
let end = words.length;
|
|
95
|
+
while (end > floor &&
|
|
96
|
+
DANGLING_TAIL_WORDS.has(words[end - 1].toLowerCase().replace(/[^\p{L}']/gu, ""))) {
|
|
97
|
+
end--;
|
|
98
|
+
}
|
|
99
|
+
return words.slice(0, end).join(" ");
|
|
100
|
+
}
|
|
75
101
|
/**
|
|
76
102
|
* Deterministically fit narration to a spoken-word `budget` so the synthesized
|
|
77
103
|
* speech lands INSIDE its scene window — instead of overrunning and either
|
|
@@ -83,7 +109,8 @@ function splitSentences(text) {
|
|
|
83
109
|
* 1. keep as many WHOLE sentences as fit within budget;
|
|
84
110
|
* 2. if even the first sentence is over budget, cap at `budget` words and back
|
|
85
111
|
* off to the last clause boundary (comma/semicolon/dash) in that span;
|
|
86
|
-
* 3.
|
|
112
|
+
* 3. drop any dangling function-word tail so the line ends on a content word;
|
|
113
|
+
* 4. always end on terminal punctuation.
|
|
87
114
|
* Never lengthens. Returns the (possibly) trimmed text + whether it changed.
|
|
88
115
|
*/
|
|
89
116
|
function fitNarrationToBudget(text, budget) {
|
|
@@ -110,7 +137,7 @@ function fitNarrationToBudget(text, budget) {
|
|
|
110
137
|
const out = clauseIdx > 0 && wordCount(capped.slice(0, clauseIdx)) >= Math.ceil(budget * 0.5)
|
|
111
138
|
? capped.slice(0, clauseIdx)
|
|
112
139
|
: capped;
|
|
113
|
-
return { text: ensureTerminalPunctuation(out), trimmed: true };
|
|
140
|
+
return { text: ensureTerminalPunctuation(trimDanglingTail(out)), trimmed: true };
|
|
114
141
|
}
|
|
115
142
|
/** Pull the salient on-screen copy from a scene so narration can complement it. */
|
|
116
143
|
function sceneCopyDigest(scene) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stock-asset resolution for the SaaS Demo Video pipeline — the "real library"
|
|
3
|
+
* half of the planner's `generatedAssets` (see DemoGeneratedAssetSchema, where
|
|
4
|
+
* `source:"stock"` requests a real photo/clip instead of an AI generation).
|
|
5
|
+
*
|
|
6
|
+
* Free, instant, and REAL: for generic real-world subjects (a laptop on a desk,
|
|
7
|
+
* a city street, a coffee shop) a licensed stock shot beats a synthesized one —
|
|
8
|
+
* so the planner can prefer it and skip the generation cost. Resolution reuses
|
|
9
|
+
* the same provider cascade the agent flow uses (Pexels → Pixabay → Unsplash for
|
|
10
|
+
* images; Pexels → Pixabay for video), gated on env keys. Fail-soft: returns
|
|
11
|
+
* null when nothing is reachable/available, and the caller marks the asset failed
|
|
12
|
+
* and strips its `gen://` ref so the scene degrades to its coded surface.
|
|
13
|
+
*
|
|
14
|
+
* Kept in `shared` (next to demoMusic) so it can import the provider services via
|
|
15
|
+
* relative paths — they are not part of the package's public surface. Deliberately
|
|
16
|
+
* cache-free: demo runs are infrequent and each stock ref resolves once per spec.
|
|
17
|
+
*/
|
|
18
|
+
import type { DemoGeneratedAsset, DemoSpec } from "../../schemas/demoSpec.schema";
|
|
19
|
+
/** Whether any stock provider is configured — the prompt only offers stock when true. */
|
|
20
|
+
export declare function stockAssetsAvailable(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve one `source:"stock"` asset to a real media URL, trying each configured
|
|
23
|
+
* provider in turn. Returns null (never throws) when no provider yields a hit.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveStockAsset(request: DemoGeneratedAsset, spec: DemoSpec): Promise<{
|
|
26
|
+
url: string;
|
|
27
|
+
attribution?: string;
|
|
28
|
+
} | null>;
|
|
29
|
+
//# sourceMappingURL=demoStockAssets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demoStockAssets.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoStockAssets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAYlF,yFAAyF;AACzF,wBAAgB,oBAAoB,IAAI,OAAO,CAM9C;AAkDD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA+CvD"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stockAssetsAvailable = stockAssetsAvailable;
|
|
4
|
+
exports.resolveStockAsset = resolveStockAsset;
|
|
5
|
+
const pexels_service_1 = require("../aiGen/providers/pexels/pexels.service");
|
|
6
|
+
const pixabay_service_1 = require("../aiGen/providers/pixabay/pixabay.service");
|
|
7
|
+
const unsplash_service_1 = require("../aiGen/providers/unsplash/unsplash.service");
|
|
8
|
+
const logger_1 = require("../../utils/logger");
|
|
9
|
+
const TAG = "[DemoStockAssets]";
|
|
10
|
+
/** Whether any stock provider is configured — the prompt only offers stock when true. */
|
|
11
|
+
function stockAssetsAvailable() {
|
|
12
|
+
return Boolean(process.env.PEXELS_API_KEY ||
|
|
13
|
+
process.env.PIXABAY_API_KEY ||
|
|
14
|
+
process.env.UNSPLASH_ACCESS_KEY);
|
|
15
|
+
}
|
|
16
|
+
/** Provider cascade — same order/precedence as the agent's search_stock tool. */
|
|
17
|
+
function buildChain(kind) {
|
|
18
|
+
const pexels = new pexels_service_1.PexelsProviderService();
|
|
19
|
+
const pixabay = new pixabay_service_1.PixabayProviderService();
|
|
20
|
+
const unsplash = new unsplash_service_1.UnsplashProviderService();
|
|
21
|
+
const chain = [
|
|
22
|
+
{
|
|
23
|
+
name: "pexels",
|
|
24
|
+
available: () => Boolean(process.env.PEXELS_API_KEY),
|
|
25
|
+
supports: () => true,
|
|
26
|
+
search: (p) => pexels.searchStock(p),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "pixabay",
|
|
30
|
+
available: () => Boolean(process.env.PIXABAY_API_KEY),
|
|
31
|
+
supports: () => true,
|
|
32
|
+
search: (p) => pixabay.searchStock(p),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "unsplash",
|
|
36
|
+
available: () => Boolean(process.env.UNSPLASH_ACCESS_KEY),
|
|
37
|
+
supports: (k) => k === "image",
|
|
38
|
+
search: (p) => unsplash.searchStock(p),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
return chain.filter((l) => l.supports(kind) && l.available());
|
|
42
|
+
}
|
|
43
|
+
/** Plain search terms from the request (`query` wins; else the prompt's first words). */
|
|
44
|
+
function searchQuery(request) {
|
|
45
|
+
const q = request.query?.trim();
|
|
46
|
+
if (q)
|
|
47
|
+
return q;
|
|
48
|
+
// Fallback: first ~6 words of the generation prompt, stripped of punctuation.
|
|
49
|
+
return request.prompt
|
|
50
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
51
|
+
.split(/\s+/)
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.slice(0, 6)
|
|
54
|
+
.join(" ");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve one `source:"stock"` asset to a real media URL, trying each configured
|
|
58
|
+
* provider in turn. Returns null (never throws) when no provider yields a hit.
|
|
59
|
+
*/
|
|
60
|
+
async function resolveStockAsset(request, spec) {
|
|
61
|
+
const kind = request.kind; // "image" | "video"
|
|
62
|
+
const chain = buildChain(kind);
|
|
63
|
+
if (chain.length === 0) {
|
|
64
|
+
logger_1.logger.warn(`${TAG} no stock provider configured — cannot resolve`, {
|
|
65
|
+
id: request.id,
|
|
66
|
+
kind,
|
|
67
|
+
});
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const params = {
|
|
71
|
+
query: searchQuery(request),
|
|
72
|
+
kind,
|
|
73
|
+
aspectRatio: request.aspect ?? spec.aspect,
|
|
74
|
+
perPage: 5,
|
|
75
|
+
...(kind === "video"
|
|
76
|
+
? { minDurationSec: 2, maxDurationSec: request.durationSec ?? 15 }
|
|
77
|
+
: {}),
|
|
78
|
+
};
|
|
79
|
+
for (const link of chain) {
|
|
80
|
+
try {
|
|
81
|
+
const result = await link.search(params);
|
|
82
|
+
const asset = result.assets?.[0];
|
|
83
|
+
if (asset?.url) {
|
|
84
|
+
logger_1.logger.info(`${TAG} resolved ${kind} "${request.id}" via ${link.name}`, {
|
|
85
|
+
query: params.query,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
url: asset.url,
|
|
89
|
+
...(asset.attribution?.name
|
|
90
|
+
? { attribution: `${asset.attribution.name} (${asset.source})` }
|
|
91
|
+
: {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
logger_1.logger.warn(`${TAG} ${link.name} returned no results`, {
|
|
95
|
+
id: request.id,
|
|
96
|
+
query: params.query,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger_1.logger.warn(`${TAG} ${link.name} search failed — trying next`, {
|
|
101
|
+
id: request.id,
|
|
102
|
+
err: err instanceof Error ? err.message : String(err),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
@@ -13,7 +13,9 @@ export * from "./demoAuthor";
|
|
|
13
13
|
export * from "./demoCredits";
|
|
14
14
|
export * from "./demoScriptPrompt";
|
|
15
15
|
export * from "./demoScriptWriter";
|
|
16
|
+
export * from "./demoCaptions";
|
|
16
17
|
export * from "./demoMusic";
|
|
18
|
+
export * from "./demoStockAssets";
|
|
17
19
|
export * from "./demoMediaGuard";
|
|
18
20
|
export * from "./demoRevisePrompt";
|
|
19
21
|
export * from "./demoReviser";
|
|
@@ -21,4 +23,5 @@ export * from "./demoPrefill";
|
|
|
21
23
|
export * from "./demoTriage";
|
|
22
24
|
export * from "./githubOAuth";
|
|
23
25
|
export * from "./githubOAuthState";
|
|
26
|
+
export * from "./productConnection.service";
|
|
24
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/demo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/demo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC"}
|
|
@@ -29,7 +29,9 @@ __exportStar(require("./demoAuthor"), exports);
|
|
|
29
29
|
__exportStar(require("./demoCredits"), exports);
|
|
30
30
|
__exportStar(require("./demoScriptPrompt"), exports);
|
|
31
31
|
__exportStar(require("./demoScriptWriter"), exports);
|
|
32
|
+
__exportStar(require("./demoCaptions"), exports);
|
|
32
33
|
__exportStar(require("./demoMusic"), exports);
|
|
34
|
+
__exportStar(require("./demoStockAssets"), exports);
|
|
33
35
|
__exportStar(require("./demoMediaGuard"), exports);
|
|
34
36
|
__exportStar(require("./demoRevisePrompt"), exports);
|
|
35
37
|
__exportStar(require("./demoReviser"), exports);
|
|
@@ -37,3 +39,4 @@ __exportStar(require("./demoPrefill"), exports);
|
|
|
37
39
|
__exportStar(require("./demoTriage"), exports);
|
|
38
40
|
__exportStar(require("./githubOAuth"), exports);
|
|
39
41
|
__exportStar(require("./githubOAuthState"), exports);
|
|
42
|
+
__exportStar(require("./productConnection.service"), exports);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { IProductConnectionModel } from "../../models/productConnection.model";
|
|
2
|
+
/**
|
|
3
|
+
* Product sign-in connection store (Tier 2 product-footage-capture —
|
|
4
|
+
* notes/PRODUCT_FOOTAGE_CAPTURE_PLAN.md). Owns the encrypt-at-write /
|
|
5
|
+
* decrypt-at-read of a user's captured browser sign-in session so both the
|
|
6
|
+
* live-session capture endpoint AND the worker's authed replay go through one
|
|
7
|
+
* place. Mirrors the githubConnection token flow (AES-256-GCM via the shared
|
|
8
|
+
* vault, `SOCIAL_TOKEN_ENC_KEY`) — plaintext state never touches Firestore.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A Playwright-compatible browser storage snapshot — the RESULT of a sign-in
|
|
12
|
+
* (never credentials). Shape matches Playwright's `storageState()` so the worker
|
|
13
|
+
* can seed cookies + localStorage before navigating past the auth wall.
|
|
14
|
+
*/
|
|
15
|
+
export interface ProductStorageState {
|
|
16
|
+
cookies: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
domain: string;
|
|
20
|
+
path: string;
|
|
21
|
+
/** Unix seconds; -1 / absent for session cookies. */
|
|
22
|
+
expires?: number;
|
|
23
|
+
httpOnly?: boolean;
|
|
24
|
+
secure?: boolean;
|
|
25
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
26
|
+
}>;
|
|
27
|
+
origins: Array<{
|
|
28
|
+
origin: string;
|
|
29
|
+
localStorage: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
value: string;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
/** Public-safe view of a connection — everything EXCEPT the encrypted state. */
|
|
36
|
+
export interface ProductConnectionSummary {
|
|
37
|
+
host: string;
|
|
38
|
+
productUrl: string;
|
|
39
|
+
origins: string[];
|
|
40
|
+
label?: string;
|
|
41
|
+
status: IProductConnectionModel["status"];
|
|
42
|
+
expiresHint?: number;
|
|
43
|
+
}
|
|
44
|
+
/** Origin host of a URL, lowercased, or null if unparseable. */
|
|
45
|
+
export declare function hostOf(url: string): string | null;
|
|
46
|
+
/** True when the vault is configured — connections are unusable without it. */
|
|
47
|
+
export declare function isProductConnectionEnabled(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Upsert a captured sign-in session (encrypted). Idempotent per (user, host):
|
|
50
|
+
* re-connecting the same product replaces the expired snapshot. Returns the
|
|
51
|
+
* host used as the connection key.
|
|
52
|
+
*/
|
|
53
|
+
export declare function saveProductConnection(args: {
|
|
54
|
+
userId: string;
|
|
55
|
+
productUrl: string;
|
|
56
|
+
storageState: ProductStorageState;
|
|
57
|
+
label?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
host: string;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Read + decrypt a user's connection for a host. Returns the storageState +
|
|
63
|
+
* metadata, or null when there's no ACTIVE connection / the vault can't decrypt
|
|
64
|
+
* (best-effort — the caller treats null as "not connected" and asks the user).
|
|
65
|
+
*/
|
|
66
|
+
export declare function getProductConnection(userId: string, host: string): Promise<{
|
|
67
|
+
state: ProductStorageState;
|
|
68
|
+
conn: IProductConnectionModel;
|
|
69
|
+
} | null>;
|
|
70
|
+
/** List a user's connections as public-safe summaries (no encrypted state). */
|
|
71
|
+
export declare function listProductConnections(userId: string): Promise<ProductConnectionSummary[]>;
|
|
72
|
+
/** Delete (revoke) a user's connection for a host. */
|
|
73
|
+
export declare function deleteProductConnection(userId: string, host: string): Promise<void>;
|
|
74
|
+
/** Flag a connection expired after a replay found the session no longer authenticates. */
|
|
75
|
+
export declare function markProductConnectionExpired(userId: string, host: string): Promise<void>;
|
|
76
|
+
/** Stamp lastUsedAt after a successful replay (observability; best-effort). */
|
|
77
|
+
export declare function touchProductConnection(userId: string, host: string): Promise<void>;
|
|
78
|
+
//# sourceMappingURL=productConnection.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"productConnection.service.d.ts","sourceRoot":"","sources":["../../../src/services/demo/productConnection.service.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,uBAAuB,EAExB,MAAM,sCAAsC,CAAC;AAG9C;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,qDAAqD;QACrD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;KACtC,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtD,CAAC,CAAC;CACJ;AAED,gFAAgF;AAChF,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMjD;AAWD,+EAA+E;AAC/E,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,mBAAmB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC5B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAAC,CAY/E;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAerC;AAED,sDAAsD;AACtD,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,0FAA0F;AAC1F,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,+EAA+E;AAC/E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAQf"}
|