vidspotai-shared 1.0.100 → 1.0.101

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 (169) hide show
  1. package/lib/globals/aiModels/enums.d.ts +1 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +3 -0
  4. package/lib/globals/aiModels/extendCapability.d.ts +26 -0
  5. package/lib/globals/aiModels/extendCapability.d.ts.map +1 -0
  6. package/lib/globals/aiModels/extendCapability.js +42 -0
  7. package/lib/globals/aiModels/index.d.ts +1 -0
  8. package/lib/globals/aiModels/index.d.ts.map +1 -1
  9. package/lib/globals/aiModels/index.js +1 -0
  10. package/lib/globals/aiModels/providers/hostedAvatars.js +10 -10
  11. package/lib/globals/types.d.ts +34 -2
  12. package/lib/globals/types.d.ts.map +1 -1
  13. package/lib/globals/types.js +37 -0
  14. package/lib/models/demoVideoProject.model.d.ts +213 -0
  15. package/lib/models/demoVideoProject.model.d.ts.map +1 -1
  16. package/lib/models/index.d.ts +2 -0
  17. package/lib/models/index.d.ts.map +1 -1
  18. package/lib/models/index.js +2 -0
  19. package/lib/models/mediaUpload.model.d.ts +25 -0
  20. package/lib/models/mediaUpload.model.d.ts.map +1 -1
  21. package/lib/models/productConnection.model.d.ts +57 -0
  22. package/lib/models/productConnection.model.d.ts.map +1 -0
  23. package/lib/models/productConnection.model.js +7 -0
  24. package/lib/models/video.model.d.ts +11 -0
  25. package/lib/models/video.model.d.ts.map +1 -1
  26. package/lib/models/videoQaJob.model.d.ts +61 -0
  27. package/lib/models/videoQaJob.model.d.ts.map +1 -0
  28. package/lib/models/videoQaJob.model.js +16 -0
  29. package/lib/schemas/brandKit.schema.d.ts +3 -1
  30. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  31. package/lib/schemas/brandKit.schema.js +9 -0
  32. package/lib/schemas/brief.schema.d.ts +5 -4
  33. package/lib/schemas/brief.schema.d.ts.map +1 -1
  34. package/lib/schemas/brief.schema.js +23 -1
  35. package/lib/schemas/demoQuestion.schema.d.ts +24 -4
  36. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -1
  37. package/lib/schemas/demoQuestion.schema.js +8 -2
  38. package/lib/schemas/demoSpec.schema.d.ts +40 -6
  39. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  40. package/lib/schemas/demoSpec.schema.js +46 -1
  41. package/lib/schemas/project.schema.d.ts +33 -3
  42. package/lib/schemas/project.schema.d.ts.map +1 -1
  43. package/lib/schemas/project.schema.js +22 -0
  44. package/lib/schemas/social.schema.d.ts +1 -1
  45. package/lib/schemas/videoPlan.schema.d.ts +22 -20
  46. package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
  47. package/lib/schemas/videoPlan.schema.js +19 -0
  48. package/lib/services/agent/editClassifier.d.ts +10 -10
  49. package/lib/services/agent/eval/types.d.ts +3 -2
  50. package/lib/services/agent/eval/types.d.ts.map +1 -1
  51. package/lib/services/agent/executor/core.d.ts.map +1 -1
  52. package/lib/services/agent/executor/core.js +11 -0
  53. package/lib/services/agent/planner/Planner.d.ts.map +1 -1
  54. package/lib/services/agent/planner/Planner.js +5 -0
  55. package/lib/services/agent/planner/overlayRegen.d.ts.map +1 -1
  56. package/lib/services/agent/planner/overlayRegen.js +5 -0
  57. package/lib/services/agent/planner/plannerMessages.d.ts.map +1 -1
  58. package/lib/services/agent/planner/plannerMessages.js +10 -1
  59. package/lib/services/agent/planner/scriptFirstPlanner.d.ts.map +1 -1
  60. package/lib/services/agent/planner/scriptFirstPlanner.js +2 -0
  61. package/lib/services/agent/regenCore.d.ts.map +1 -1
  62. package/lib/services/agent/regenCore.js +11 -1
  63. package/lib/services/agent/runHelpers.d.ts +8 -0
  64. package/lib/services/agent/runHelpers.d.ts.map +1 -1
  65. package/lib/services/agent/runHelpers.js +5 -1
  66. package/lib/services/agent/tools/chapterOutline.tool.d.ts +8 -0
  67. package/lib/services/agent/tools/chapterOutline.tool.d.ts.map +1 -1
  68. package/lib/services/agent/tools/chapterOutline.tool.js +21 -8
  69. package/lib/services/agent/tools/composeScene.tool.d.ts +32 -2
  70. package/lib/services/agent/tools/composeScene.tool.d.ts.map +1 -1
  71. package/lib/services/agent/tools/estimateCost.tool.d.ts +11 -1
  72. package/lib/services/agent/tools/estimateCost.tool.d.ts.map +1 -1
  73. package/lib/services/agent/tools/extendVideo.tool.d.ts +9 -3
  74. package/lib/services/agent/tools/extendVideo.tool.d.ts.map +1 -1
  75. package/lib/services/agent/tools/extendVideo.tool.js +12 -27
  76. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +1 -1
  77. package/lib/services/agent/tools/generateScript.tool.d.ts +3 -2
  78. package/lib/services/agent/tools/generateScript.tool.d.ts.map +1 -1
  79. package/lib/services/agent/tools/generateScript.tool.js +16 -7
  80. package/lib/services/agent/tools/generateVoiceover.tool.d.ts +1 -1
  81. package/lib/services/agent/tools/planVideo.tool.d.ts +11 -8
  82. package/lib/services/agent/tools/planVideo.tool.d.ts.map +1 -1
  83. package/lib/services/agent/tools/render.tool.d.ts +11 -1
  84. package/lib/services/agent/tools/render.tool.d.ts.map +1 -1
  85. package/lib/services/agent/tools/searchStock.tool.d.ts +1 -1
  86. package/lib/services/agent/tools/searchUserLibrary.tool.d.ts +4 -4
  87. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  88. package/lib/services/aiGen/providers/kling/kling.service.js +22 -3
  89. package/lib/services/aiGen/providers/kling/klingStatus.d.ts +7 -0
  90. package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
  91. package/lib/services/aiGen/providers/kling/klingStatus.js +41 -4
  92. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
  93. package/lib/services/aiGen/providers/pixverse/pixverse.service.js +25 -0
  94. package/lib/services/aiGen/providers/types.d.ts +20 -0
  95. package/lib/services/aiGen/providers/types.d.ts.map +1 -1
  96. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -1
  97. package/lib/services/avatarGen/avatarProviderFactory.js +3 -0
  98. package/lib/services/avatarGen/index.d.ts +1 -0
  99. package/lib/services/avatarGen/index.d.ts.map +1 -1
  100. package/lib/services/avatarGen/index.js +1 -0
  101. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts +53 -0
  102. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.d.ts.map +1 -0
  103. package/lib/services/avatarGen/providers/synthesiaAvatarProvider.js +176 -0
  104. package/lib/services/demo/demoAuthor.d.ts +7 -0
  105. package/lib/services/demo/demoAuthor.d.ts.map +1 -1
  106. package/lib/services/demo/demoAuthor.js +2 -0
  107. package/lib/services/demo/demoAuthorPrompt.d.ts +6 -0
  108. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -1
  109. package/lib/services/demo/demoAuthorPrompt.js +24 -2
  110. package/lib/services/demo/demoCaptions.d.ts +69 -0
  111. package/lib/services/demo/demoCaptions.d.ts.map +1 -0
  112. package/lib/services/demo/demoCaptions.js +152 -0
  113. package/lib/services/demo/demoMusic.d.ts +8 -1
  114. package/lib/services/demo/demoMusic.d.ts.map +1 -1
  115. package/lib/services/demo/demoMusic.js +32 -16
  116. package/lib/services/demo/demoScriptPrompt.d.ts +2 -1
  117. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -1
  118. package/lib/services/demo/demoScriptPrompt.js +29 -2
  119. package/lib/services/demo/demoStockAssets.d.ts +29 -0
  120. package/lib/services/demo/demoStockAssets.d.ts.map +1 -0
  121. package/lib/services/demo/demoStockAssets.js +107 -0
  122. package/lib/services/demo/index.d.ts +3 -0
  123. package/lib/services/demo/index.d.ts.map +1 -1
  124. package/lib/services/demo/index.js +3 -0
  125. package/lib/services/demo/productConnection.service.d.ts +78 -0
  126. package/lib/services/demo/productConnection.service.d.ts.map +1 -0
  127. package/lib/services/demo/productConnection.service.js +125 -0
  128. package/lib/services/editor/planToProject.d.ts.map +1 -1
  129. package/lib/services/editor/planToProject.helpers.d.ts +1 -0
  130. package/lib/services/editor/planToProject.helpers.d.ts.map +1 -1
  131. package/lib/services/editor/planToProject.helpers.js +3 -0
  132. package/lib/services/editor/planToProject.js +39 -0
  133. package/lib/services/editor/projectToDesign.js +13 -1
  134. package/lib/services/firestore.service.d.ts +14 -0
  135. package/lib/services/firestore.service.d.ts.map +1 -1
  136. package/lib/services/firestore.service.js +16 -0
  137. package/lib/services/translation/index.d.ts +1 -0
  138. package/lib/services/translation/index.d.ts.map +1 -1
  139. package/lib/services/translation/index.js +8 -1
  140. package/lib/services/translation/outputLanguage.d.ts +75 -0
  141. package/lib/services/translation/outputLanguage.d.ts.map +1 -0
  142. package/lib/services/translation/outputLanguage.js +190 -0
  143. package/lib/services/translation/translation.service.d.ts +13 -2
  144. package/lib/services/translation/translation.service.d.ts.map +1 -1
  145. package/lib/services/translation/translation.service.js +66 -5
  146. package/lib/services/tts/index.d.ts +1 -0
  147. package/lib/services/tts/index.d.ts.map +1 -1
  148. package/lib/services/tts/index.js +1 -0
  149. package/lib/services/tts/providers/elevenlabs.service.d.ts +7 -0
  150. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  151. package/lib/services/tts/providers/elevenlabs.service.js +69 -13
  152. package/lib/services/tts/types.d.ts +29 -0
  153. package/lib/services/tts/types.d.ts.map +1 -1
  154. package/lib/services/tts/wordTimings.d.ts +27 -0
  155. package/lib/services/tts/wordTimings.d.ts.map +1 -0
  156. package/lib/services/tts/wordTimings.js +57 -0
  157. package/lib/utils/billableDuration.d.ts +11 -0
  158. package/lib/utils/billableDuration.d.ts.map +1 -0
  159. package/lib/utils/billableDuration.js +91 -0
  160. package/lib/utils/index.d.ts +3 -0
  161. package/lib/utils/index.d.ts.map +1 -1
  162. package/lib/utils/index.js +3 -0
  163. package/lib/utils/mp4Duration.d.ts +38 -0
  164. package/lib/utils/mp4Duration.d.ts.map +1 -0
  165. package/lib/utils/mp4Duration.js +164 -0
  166. package/lib/utils/socialEntitlements.d.ts +51 -0
  167. package/lib/utils/socialEntitlements.d.ts.map +1 -0
  168. package/lib/utils/socialEntitlements.js +65 -0
  169. package/package.json +1 -1
@@ -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
- try {
52
- const controller = new AbortController();
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. always end on terminal punctuation.
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;AA4BD;;;;;;;;;;;;;GAaG;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"}
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. always end on terminal punctuation.
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"}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hostOf = hostOf;
4
+ exports.isProductConnectionEnabled = isProductConnectionEnabled;
5
+ exports.saveProductConnection = saveProductConnection;
6
+ exports.getProductConnection = getProductConnection;
7
+ exports.listProductConnections = listProductConnections;
8
+ exports.deleteProductConnection = deleteProductConnection;
9
+ exports.markProductConnectionExpired = markProductConnectionExpired;
10
+ exports.touchProductConnection = touchProductConnection;
11
+ const firestore_1 = require("firebase-admin/firestore");
12
+ const firestore_service_1 = require("../firestore.service");
13
+ const productConnection_model_1 = require("../../models/productConnection.model");
14
+ const tokenVault_1 = require("../crypto/tokenVault");
15
+ /** Origin host of a URL, lowercased, or null if unparseable. */
16
+ function hostOf(url) {
17
+ try {
18
+ return new URL(url).host.toLowerCase();
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ /** Earliest real cookie expiry (epoch ms) across the snapshot, for the reconnect UX. */
25
+ function earliestCookieExpiry(state) {
26
+ const future = state.cookies
27
+ .map((c) => c.expires)
28
+ .filter((e) => typeof e === "number" && e > 0)
29
+ .map((e) => e * 1000);
30
+ return future.length ? Math.min(...future) : undefined;
31
+ }
32
+ /** True when the vault is configured — connections are unusable without it. */
33
+ function isProductConnectionEnabled() {
34
+ return (0, tokenVault_1.isTokenVaultConfigured)();
35
+ }
36
+ /**
37
+ * Upsert a captured sign-in session (encrypted). Idempotent per (user, host):
38
+ * re-connecting the same product replaces the expired snapshot. Returns the
39
+ * host used as the connection key.
40
+ */
41
+ async function saveProductConnection(args) {
42
+ const { userId, productUrl, storageState, label } = args;
43
+ const host = hostOf(productUrl);
44
+ if (!host)
45
+ throw new Error("saveProductConnection: unparseable productUrl");
46
+ const origins = [
47
+ ...new Set(storageState.origins
48
+ .map((o) => o.origin)
49
+ .concat(new URL(productUrl).origin)),
50
+ ];
51
+ const expiresHint = earliestCookieExpiry(storageState);
52
+ const stateEnc = (0, tokenVault_1.encryptToken)(JSON.stringify(storageState));
53
+ const doc = {
54
+ userId,
55
+ host,
56
+ productUrl,
57
+ origins,
58
+ ...(label ? { label } : {}),
59
+ stateEnc,
60
+ ...(expiresHint ? { expiresHint } : {}),
61
+ status: "active",
62
+ capturedAt: firestore_1.FieldValue.serverTimestamp(),
63
+ updatedAt: firestore_1.FieldValue.serverTimestamp(),
64
+ };
65
+ await firestore_service_1.FirestoreService.productConnectionsCol
66
+ .doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
67
+ .set(doc);
68
+ return { host };
69
+ }
70
+ /**
71
+ * Read + decrypt a user's connection for a host. Returns the storageState +
72
+ * metadata, or null when there's no ACTIVE connection / the vault can't decrypt
73
+ * (best-effort — the caller treats null as "not connected" and asks the user).
74
+ */
75
+ async function getProductConnection(userId, host) {
76
+ const snap = await firestore_service_1.FirestoreService.productConnectionsCol
77
+ .doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
78
+ .get();
79
+ const conn = snap.data();
80
+ if (!conn || conn.status !== "active" || !conn.stateEnc)
81
+ return null;
82
+ try {
83
+ const state = JSON.parse((0, tokenVault_1.decryptToken)(conn.stateEnc));
84
+ return { state, conn };
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ /** List a user's connections as public-safe summaries (no encrypted state). */
91
+ async function listProductConnections(userId) {
92
+ const snap = await firestore_service_1.FirestoreService.productConnectionsCol
93
+ .where("userId", "==", userId)
94
+ .get();
95
+ return snap.docs.map((d) => {
96
+ const c = d.data();
97
+ return {
98
+ host: c.host,
99
+ productUrl: c.productUrl,
100
+ origins: c.origins,
101
+ ...(c.label ? { label: c.label } : {}),
102
+ status: c.status,
103
+ ...(c.expiresHint ? { expiresHint: c.expiresHint } : {}),
104
+ };
105
+ });
106
+ }
107
+ /** Delete (revoke) a user's connection for a host. */
108
+ async function deleteProductConnection(userId, host) {
109
+ await firestore_service_1.FirestoreService.productConnectionsCol
110
+ .doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
111
+ .delete();
112
+ }
113
+ /** Flag a connection expired after a replay found the session no longer authenticates. */
114
+ async function markProductConnectionExpired(userId, host) {
115
+ await firestore_service_1.FirestoreService.productConnectionsCol
116
+ .doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
117
+ .set({ status: "expired", updatedAt: firestore_1.FieldValue.serverTimestamp() }, { merge: true });
118
+ }
119
+ /** Stamp lastUsedAt after a successful replay (observability; best-effort). */
120
+ async function touchProductConnection(userId, host) {
121
+ await firestore_service_1.FirestoreService.productConnectionsCol
122
+ .doc((0, productConnection_model_1.productConnectionDocId)(userId, host))
123
+ .set({ lastUsedAt: firestore_1.FieldValue.serverTimestamp(), updatedAt: firestore_1.FieldValue.serverTimestamp() }, { merge: true })
124
+ .catch(() => { });
125
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"planToProject.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,OAAO,EAIR,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAehE,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAwZhE"}
1
+ {"version":3,"file":"planToProject.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,OAAO,EAIR,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAehE,OAAO,EAAE,QAAQ,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CA6bhE"}
@@ -15,6 +15,7 @@ export declare const TRACK_ID: {
15
15
  readonly music: "track-music";
16
16
  readonly text: "track-text";
17
17
  readonly caption: "track-caption";
18
+ readonly logo: "track-logo";
18
19
  };
19
20
  export declare function isStillStrategy(s: SceneAssetStrategy): boolean;
20
21
  /** A passable URL we can put in skeleton clips so ProjectSchema.url() passes. */
@@ -1 +1 @@
1
- {"version":3,"file":"planToProject.helpers.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,UAAU,EACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,cAAc,EACd,SAAS,EACV,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;GAMG;AAIH,eAAO,MAAM,QAAQ;;;;;;;CASX,CAAC;AASX,wBAAgB,eAAe,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAE9D;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAInD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAW5F;AAID,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,CASjF,CAAC;AAIF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,kBAAkB,GAAG,SAAS,GACtC,UAAU,CAiBZ;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,WAAW,EAAE,MAAM;AACnB;;;;GAIG;AACH,SAAS,CAAC,EAAE,aAAa,GACxB,QAAQ,EAAE,CA2CZ;AAED,wBAAgB,SAAS,CACvB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,KAAK,CAYP"}
1
+ {"version":3,"file":"planToProject.helpers.d.ts","sourceRoot":"","sources":["../../../src/services/editor/planToProject.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,UAAU,EACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,cAAc,EACd,SAAS,EACV,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE;;;;;;GAMG;AAIH,eAAO,MAAM,QAAQ;;;;;;;;CAYX,CAAC;AASX,wBAAgB,eAAe,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAE9D;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAInD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAW5F;AAID,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,IAAI,CASjF,CAAC;AAIF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,kBAAkB,GAAG,SAAS,GACtC,UAAU,CAiBZ;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,WAAW,EAAE,MAAM;AACnB;;;;GAIG;AACH,SAAS,CAAC,EAAE,aAAa,GACxB,QAAQ,EAAE,CA2CZ;AAED,wBAAgB,SAAS,CACvB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,MAAM,GACrB,KAAK,CAYP"}
@@ -24,6 +24,9 @@ exports.TRACK_ID = {
24
24
  music: "track-music",
25
25
  text: "track-text",
26
26
  caption: "track-caption",
27
+ // Brand-logo overlay lane — a single image clip pinned to a corner, spanning
28
+ // the whole video, painted on top of everything (Reach per-recipient logo).
29
+ logo: "track-logo",
27
30
  };
28
31
  // ─── strategy → clip kind ────────────────────────────────────────────────
29
32
  const STILL_STRATEGIES = new Set([
@@ -335,6 +335,39 @@ function planToProject(input) {
335
335
  };
336
336
  clips.push(musicClip);
337
337
  }
338
+ // Reach per-recipient brand logo — a single contained ImageClip pinned to the
339
+ // top-right corner, spanning the whole video, painted on top of every track.
340
+ // Only emitted when the outreach fan-out injected plan.brandLogoUrl (the
341
+ // planner never sets it), so scene visuals are untouched for every other flow.
342
+ let hasLogo = false;
343
+ if (plan.brandLogoUrl && offsetMs > 0) {
344
+ hasLogo = true;
345
+ // Square box (contain letterboxes any logo aspect — no crop), ~16% of the
346
+ // short side, inset by a ~4%-of-width margin from the top-right corner.
347
+ const side = Math.round(Math.min(size.width, size.height) * 0.16);
348
+ const margin = Math.round(size.width * 0.04);
349
+ const logoClip = {
350
+ id: "brand-logo",
351
+ trackId: planToProject_helpers_1.TRACK_ID.logo,
352
+ type: "image",
353
+ display: { from: 0, to: offsetMs },
354
+ name: "Brand logo",
355
+ src: plan.brandLogoUrl,
356
+ fit: "contain",
357
+ box: {
358
+ left: size.width - side - margin,
359
+ top: margin,
360
+ width: side,
361
+ height: side,
362
+ },
363
+ provenance: { tier: "T0", source: "user-upload", creditsSpent: 0 },
364
+ effects: [],
365
+ transform: { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0, opacity: 1 },
366
+ locked: false,
367
+ metadata: { brandLogo: true },
368
+ };
369
+ clips.push(logoClip);
370
+ }
338
371
  // Stage 7 (lock enforcement) — overlay preserved clips verbatim. Runs after
339
372
  // ALL clips (incl. music) are built so a preserved clip cleanly displaces
340
373
  // its freshly-rebuilt twin by id (ids are deterministic: scene-N-visual /
@@ -376,6 +409,9 @@ function planToProject(input) {
376
409
  case planToProject_helpers_1.TRACK_ID.caption:
377
410
  hasCaptions = true;
378
411
  break;
412
+ case planToProject_helpers_1.TRACK_ID.logo:
413
+ hasLogo = true;
414
+ break;
379
415
  default: break; // visual track is always declared
380
416
  }
381
417
  }
@@ -400,6 +436,9 @@ function planToProject(input) {
400
436
  // row per source group, and regen/new captions route back onto it.
401
437
  if (hasCaptions)
402
438
  tracks.push((0, planToProject_helpers_1.makeTrack)(planToProject_helpers_1.TRACK_ID.caption, "caption", trackIndex++, "Voiceover · Captions", planToProject_helpers_1.TRACK_ID.voiceover));
439
+ // Logo lane is declared LAST → highest track index → painted on top of all.
440
+ if (hasLogo)
441
+ tracks.push((0, planToProject_helpers_1.makeTrack)(planToProject_helpers_1.TRACK_ID.logo, "video", trackIndex++, "Logo"));
403
442
  const durationMs = Math.max(1, offsetMs);
404
443
  return {
405
444
  schemaVersion: project_schema_1.PROJECT_SCHEMA_VERSION,
@@ -153,7 +153,19 @@ function videoDetails(clip) {
153
153
  };
154
154
  }
155
155
  function imageDetails(clip) {
156
- return { src: clip.src };
156
+ const d = { src: clip.src };
157
+ // Positioned overlay images (e.g. a corner brand logo) carry an explicit
158
+ // pixel box + fit; the renderer's container/media styles read left/top/
159
+ // width/height/objectFit. Absent → full-frame cover as before.
160
+ if (clip.box) {
161
+ d.left = clip.box.left;
162
+ d.top = clip.box.top;
163
+ d.width = clip.box.width;
164
+ d.height = clip.box.height;
165
+ }
166
+ if (clip.fit)
167
+ d.objectFit = clip.fit;
168
+ return d;
157
169
  }
158
170
  function audioDetails(clip) {
159
171
  return {