@vanillaskyai/video 0.10.0 → 0.10.2
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/CHANGELOG.md +13 -0
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-ZDJNFA52.js → chunk-5BSJLT6H.js} +6 -6
- package/dist/{chunk-NMFDC2W2.js → chunk-5C6HZNHY.js} +9 -4
- package/dist/{chunk-ELVOT2FQ.js → chunk-6KZGF63O.js} +46 -11
- package/dist/{chunk-2HFO22K2.js → chunk-JKVOBTRO.js} +3 -3
- package/dist/{chunk-XX4GXKTX.js → chunk-KHLO5OHT.js} +1 -1
- package/dist/{chunk-PAJ44TFW.js → chunk-LVM5Q2DL.js} +8 -6
- package/dist/{chunk-Z67Q5KUW.js → chunk-VQH3JTQC.js} +1 -1
- package/dist/{cinema-media-C7OUPNSH.js → cinema-media-APDJS7SC.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/{comparison-GNZOR7MF.js → comparison-ZVE2DE7U.js} +2 -2
- package/dist/{compose-video-TBFTONUC.js → compose-video-BH5K6XWT.js} +1 -1
- package/dist/{editorial-timeline-EMV5335S.js → editorial-timeline-YIXXVJUV.js} +2 -2
- package/dist/{key-figure-P37VK425.js → key-figure-I2C37FWV.js} +2 -2
- package/dist/{mobile-message-OF7T62B7.js → mobile-message-JSGBU6EX.js} +2 -2
- package/dist/{quote-T6WYLN7A.js → quote-XH54G3FS.js} +2 -2
- package/dist/react.js +63 -19
- package/dist/server.js +45 -32
- package/dist/{system-prompt-SYRT5DWM.js → system-prompt-AG26KJAA.js} +1 -1
- package/dist/test.js +1 -1
- package/package.json +1 -1
- package/registry/items/cinemaMedia.json +1 -1
- package/registry/items/comparison.json +1 -1
- package/registry/items/editorialTimeline.json +1 -1
- package/registry/items/keyFigure.json +1 -1
- package/registry/items/mobileMessage.json +1 -1
- package/registry/items/quote.json +1 -1
- package/starters/video-chat/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.10.2
|
|
8
|
+
|
|
9
|
+
- Keep generated narration playing in Safari after a delayed opening-to-body handoff by reusing the permitted audio element; isolate late playback failures from subsequent lines.
|
|
10
|
+
- In drop mode, a media miss without usable authored fallback skips only that scene and preserves later valid scenes and the ending. Strict mode still fails. The skipped beat is reported; its content cannot be reconstructed without a usable asset or fallback.
|
|
11
|
+
- Guide first-shot narration to fit its five-second footage budget and develop the answer in later scenes, without imposing a fixed sentence length on the rest of the video.
|
|
12
|
+
|
|
13
|
+
## 0.10.1
|
|
14
|
+
|
|
15
|
+
- Continue narration through failed built-in media using the authored fallback or the existing graphic on black. Keep the recovery anchor after URL resolution, and observe actual mounted images for photo readiness.
|
|
16
|
+
|
|
17
|
+
- Match planning, narration, visual progression and endings to explanations, fiction, comedy, imaginative requests and practical answers. Remove conflicting instructions that forced a factual arc, fixed sentence length or a different template for every beat.
|
|
18
|
+
- Ask follow-up generation to exclude questions already answered by the full narration, including paraphrases, and to preserve the conversation’s intent.
|
|
19
|
+
|
|
7
20
|
## 0.10.0
|
|
8
21
|
|
|
9
22
|
### Breaking
|
package/dist/check-runtime.js
CHANGED
|
@@ -114,15 +114,15 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
|
|
|
114
114
|
"Add that scene before resolving any stock or supplied asset. Resolve media as part of each complete later body scene."
|
|
115
115
|
],
|
|
116
116
|
"Use only claims supported by the factual basis permitted by the trusted system prompt.",
|
|
117
|
-
"
|
|
117
|
+
"Develop the requested answer within the duration: preserve its mechanism, essential actions, story resolution or punchline. A short prompt can need several distinct visual beats; input length is not the answer length.",
|
|
118
118
|
"For a long source, summarize instead of attempting to represent every fact, unless the creative instructions explicitly request complete fact coverage that fits the duration.",
|
|
119
119
|
"When selecting claims from the raw input, preserve their exact wording and numbers. Preserve qualifiers, units, denominators, ranges, and comparison direction; for example, do not shorten 4.8 out of 5 to 4.8.",
|
|
120
|
-
"Choose the scene count from the
|
|
120
|
+
"Choose the scene count from the requested intent, visual progression and duration budget. A joke can be brief; a practical sequence or imaginative story may need more development. Do not stop while an essential step or payoff is missing.",
|
|
121
121
|
"If the creative instructions explicitly require one separate scene per named item, release, section, or list entry, do not merge, group, or omit those required items. Keep related required scenes adjacent in a coherent progression while preserving each item as its own scene.",
|
|
122
122
|
"Before emitting, verify that the explicitly requested structure can fit readably within the maximum duration. If it cannot, preserve readability and the requested separation for the scenes that fit, then finish with plan.complete using finishReason length rather than silently changing the structure.",
|
|
123
|
-
"
|
|
124
|
-
"Emit exactly one final scene.add with placement closer.
|
|
125
|
-
"
|
|
123
|
+
"Before plan.complete, check that the answer fulfills the actual request. Do not mistake a hook and a single supporting statement for a complete explanation, or explain a joke after its punchline.",
|
|
124
|
+
"Emit exactly one final scene.add with placement closer. Land the requested ending without repeating the opening or implying another scene follows.",
|
|
125
|
+
"Reuse the same suitable template when it serves successive beats. Vary the subject action, shot scale, perspective or purposeful detail, not the template merely for variety.",
|
|
126
126
|
"Never add filler to satisfy a count or diversity target.",
|
|
127
127
|
input.suppliedMedia?.length ? input.opening === false ? "Select zero or more relevant opaque supplied-media references for visible scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "Select zero or more relevant opaque supplied-media references for visible later scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster." : input.opening === false ? "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent, which may begin on the first scene. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster.",
|
|
128
128
|
"",
|
|
@@ -691,7 +691,7 @@ function createVideo(rawInput, options) {
|
|
|
691
691
|
position
|
|
692
692
|
}));
|
|
693
693
|
}
|
|
694
|
-
const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-
|
|
694
|
+
const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-AG26KJAA.js")).createVideoSystemPrompt(input.knowledgeMode);
|
|
695
695
|
const context = { request, systemPrompt, userPrompt, initialConfig, signal: controller.signal };
|
|
696
696
|
attachGenerationLifecycleSink(context, lifecycle);
|
|
697
697
|
for await (const untrustedPart of options.generate(context)) {
|
|
@@ -163,17 +163,22 @@ var SceneBackground = ({
|
|
|
163
163
|
onError: () => setMediaPaint("failed")
|
|
164
164
|
}
|
|
165
165
|
) : /* @__PURE__ */ jsx(
|
|
166
|
-
"
|
|
166
|
+
"img",
|
|
167
167
|
{
|
|
168
|
+
src: mediaUrl,
|
|
169
|
+
alt: "",
|
|
170
|
+
"aria-hidden": "true",
|
|
171
|
+
draggable: false,
|
|
168
172
|
"data-media-position": mediaPosition,
|
|
169
173
|
style: {
|
|
170
174
|
position: "absolute",
|
|
171
175
|
inset: 0,
|
|
172
176
|
transform: bgTransform.transform,
|
|
173
177
|
transformOrigin: bgTransform.transformOrigin,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
width: "100%",
|
|
179
|
+
height: "100%",
|
|
180
|
+
objectFit: "cover",
|
|
181
|
+
objectPosition: resolvedPosition
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
)),
|
|
@@ -21,10 +21,12 @@ import { createContext, useContext, useEffect, useRef } from "react";
|
|
|
21
21
|
import { jsx } from "react/jsx-runtime";
|
|
22
22
|
var MountedReadinessContext = createContext(void 0);
|
|
23
23
|
var sceneReadinessKey = (scene) => `${scene.id}\0${String(scene.variables.mediaUrl || "")}`;
|
|
24
|
-
function MountedSceneReadiness({ scene, playing, fallback = false }) {
|
|
24
|
+
function MountedSceneReadiness({ scene, playing, fallback = false, onFailure }) {
|
|
25
25
|
const marker = useRef(null);
|
|
26
26
|
const report = useContext(MountedReadinessContext);
|
|
27
27
|
const key = sceneReadinessKey(scene);
|
|
28
|
+
const onFailureRef = useRef(onFailure);
|
|
29
|
+
onFailureRef.current = onFailure;
|
|
28
30
|
useEffect(() => {
|
|
29
31
|
if (!report || !playing) return;
|
|
30
32
|
let stopped = false;
|
|
@@ -35,13 +37,14 @@ function MountedSceneReadiness({ scene, playing, fallback = false }) {
|
|
|
35
37
|
const finish = (error, actualVideoFrame = false) => {
|
|
36
38
|
if (!stopped) {
|
|
37
39
|
stopped = true;
|
|
38
|
-
|
|
40
|
+
if (error && onFailureRef.current) onFailureRef.current();
|
|
41
|
+
else report(key, error, actualVideoFrame);
|
|
39
42
|
}
|
|
40
43
|
};
|
|
41
44
|
const check = () => {
|
|
42
45
|
if (stopped) return;
|
|
43
46
|
const root = marker.current?.closest("[data-video-frame]");
|
|
44
|
-
if (fallback && root?.querySelector("[data-scene-fallback]")) {
|
|
47
|
+
if (fallback && root?.querySelector("[data-scene-fallback]") && !root.querySelector("[data-template-loading]") && document.fonts?.status !== "loading") {
|
|
45
48
|
finish();
|
|
46
49
|
return;
|
|
47
50
|
}
|
|
@@ -132,6 +135,7 @@ import {
|
|
|
132
135
|
lazy,
|
|
133
136
|
Suspense,
|
|
134
137
|
useState,
|
|
138
|
+
useCallback,
|
|
135
139
|
useEffect as useEffect2,
|
|
136
140
|
useSyncExternalStore
|
|
137
141
|
} from "react";
|
|
@@ -269,7 +273,9 @@ function SceneLayer({
|
|
|
269
273
|
zIndex,
|
|
270
274
|
externalVideoBackdrop
|
|
271
275
|
}) {
|
|
272
|
-
const
|
|
276
|
+
const mediaFailed = externalVideoBackdrop === "fallback";
|
|
277
|
+
const recoveryTitle = mediaFailed && range.scene.templateId === "cinemaMedia" && typeof range.scene.variables.fallbackText === "string" ? range.scene.variables.fallbackText : void 0;
|
|
278
|
+
const template = kit.getTemplate(recoveryTitle ? "chapterTitle" : range.scene.templateId);
|
|
273
279
|
const duration = range.end - range.start;
|
|
274
280
|
return /* @__PURE__ */ jsx2(
|
|
275
281
|
ExternalVideoBackdropProvider,
|
|
@@ -281,6 +287,7 @@ function SceneLayer({
|
|
|
281
287
|
"div",
|
|
282
288
|
{
|
|
283
289
|
"data-scene-layer": layer,
|
|
290
|
+
"data-scene-fallback": mediaFailed ? "true" : void 0,
|
|
284
291
|
"data-layer-scene-id": range.scene.id,
|
|
285
292
|
"data-layer-template-id": range.scene.templateId,
|
|
286
293
|
"aria-hidden": interactive ? void 0 : true,
|
|
@@ -308,7 +315,9 @@ function SceneLayer({
|
|
|
308
315
|
createElement(template.component, {
|
|
309
316
|
variables: {
|
|
310
317
|
...template.defaults ?? getTemplateDefaults(template.schema),
|
|
311
|
-
...range.scene.variables
|
|
318
|
+
...range.scene.variables,
|
|
319
|
+
...mediaFailed ? { mediaUrl: "", mediaPoster: "", mediaType: "gradient" } : {},
|
|
320
|
+
...recoveryTitle ? { title: recoveryTitle } : {}
|
|
312
321
|
},
|
|
313
322
|
style: config.style,
|
|
314
323
|
progress,
|
|
@@ -344,7 +353,18 @@ function VideoFrame({
|
|
|
344
353
|
className,
|
|
345
354
|
style
|
|
346
355
|
}) {
|
|
347
|
-
const [
|
|
356
|
+
const [failedMedia, setFailedMedia] = useState(() => /* @__PURE__ */ new Set());
|
|
357
|
+
const markMediaFailed = useCallback((key) => {
|
|
358
|
+
if (!key || !config.scenes.some((scene) => sceneReadinessKey(scene) === key)) return;
|
|
359
|
+
setFailedMedia((previous) => previous.has(key) ? previous : /* @__PURE__ */ new Set([...previous, key]));
|
|
360
|
+
}, [config.scenes]);
|
|
361
|
+
useEffect2(() => {
|
|
362
|
+
const currentKeys = new Set(config.scenes.map(sceneReadinessKey));
|
|
363
|
+
setFailedMedia((previous) => {
|
|
364
|
+
const retained = new Set([...previous].filter((key) => currentKeys.has(key)));
|
|
365
|
+
return retained.size === previous.size ? previous : retained;
|
|
366
|
+
});
|
|
367
|
+
}, [config.scenes]);
|
|
348
368
|
const [readyPersistentVideo, setReadyPersistentVideo] = useState();
|
|
349
369
|
const decoderConstrainedDevice = useDecoderConstraint();
|
|
350
370
|
const timeline = resolveVideoTimeline(config);
|
|
@@ -422,7 +442,8 @@ function VideoFrame({
|
|
|
422
442
|
mediaPosition: String(posterPreparationRange.scene.variables.mediaPosition || "center"),
|
|
423
443
|
backgroundEffect: posterPreparationRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect
|
|
424
444
|
} : void 0;
|
|
425
|
-
const
|
|
445
|
+
const activeMediaFailed = failedMedia.has(sceneReadinessKey(active.scene));
|
|
446
|
+
const persistentVideoFailed = persistentVideoKey !== void 0 && failedMedia.has(persistentVideoKey);
|
|
426
447
|
const persistentVideoReady = persistentVideoRange !== void 0 && (String(persistentVideoRange.scene.variables.mediaPoster || "") !== "" || readyPersistentVideo === persistentVideoKey);
|
|
427
448
|
const persistentVideoMode = persistentVideoFailed ? "fallback" : persistentVideoReady ? "ready" : "pending";
|
|
428
449
|
const mountingNext = Boolean(
|
|
@@ -442,6 +463,12 @@ function VideoFrame({
|
|
|
442
463
|
return /* @__PURE__ */ jsxs(
|
|
443
464
|
"div",
|
|
444
465
|
{
|
|
466
|
+
onErrorCapture: (event) => {
|
|
467
|
+
const target = event.target;
|
|
468
|
+
if ((target instanceof HTMLVideoElement || target instanceof HTMLImageElement) && target.getAttribute("src") === active.scene.variables.mediaUrl && supportsExternalVideoBackdrop(activeTemplate) && (target.closest("[data-layer-scene-id]")?.getAttribute("data-layer-scene-id") === active.scene.id || target.closest("[data-persistent-video-scene-id]")?.getAttribute("data-persistent-video-scene-id") === active.scene.id)) {
|
|
469
|
+
markMediaFailed(sceneReadinessKey(active.scene));
|
|
470
|
+
}
|
|
471
|
+
},
|
|
445
472
|
"data-video-frame": "ready",
|
|
446
473
|
"data-scene-id": active.scene.id,
|
|
447
474
|
"data-template-id": active.scene.templateId,
|
|
@@ -455,7 +482,15 @@ function VideoFrame({
|
|
|
455
482
|
...style
|
|
456
483
|
},
|
|
457
484
|
children: [
|
|
458
|
-
/* @__PURE__ */ jsx2(
|
|
485
|
+
/* @__PURE__ */ jsx2(
|
|
486
|
+
MountedSceneReadiness,
|
|
487
|
+
{
|
|
488
|
+
scene: active.scene,
|
|
489
|
+
playing,
|
|
490
|
+
fallback: activeMediaFailed,
|
|
491
|
+
onFailure: sceneHasBackdrop(active) && supportsExternalVideoBackdrop(activeTemplate) && !activeMediaFailed ? () => markMediaFailed(sceneReadinessKey(active.scene)) : void 0
|
|
492
|
+
}
|
|
493
|
+
),
|
|
459
494
|
preparedReadinessRange && /* @__PURE__ */ jsx2(PreparedSceneReadiness, { scene: preparedReadinessRange.scene }),
|
|
460
495
|
/* @__PURE__ */ jsxs(
|
|
461
496
|
"div",
|
|
@@ -530,7 +565,7 @@ function VideoFrame({
|
|
|
530
565
|
opacity: decoderConstrainedTransition ? blendProgress : 0
|
|
531
566
|
} : void 0,
|
|
532
567
|
onReady: () => setReadyPersistentVideo(persistentVideoKey),
|
|
533
|
-
onError: () =>
|
|
568
|
+
onError: () => markMediaFailed(persistentVideoKey)
|
|
534
569
|
}
|
|
535
570
|
) })
|
|
536
571
|
}
|
|
@@ -554,7 +589,7 @@ function VideoFrame({
|
|
|
554
589
|
opacity: 1 - blendProgress,
|
|
555
590
|
interactive: true,
|
|
556
591
|
zIndex: 1,
|
|
557
|
-
externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
|
|
592
|
+
externalVideoBackdrop: activeMediaFailed ? "fallback" : persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
|
|
558
593
|
},
|
|
559
594
|
active.scene.id
|
|
560
595
|
),
|
|
@@ -598,7 +633,7 @@ function VideoFrame({
|
|
|
598
633
|
opacity: 1,
|
|
599
634
|
interactive: true,
|
|
600
635
|
zIndex: 1,
|
|
601
|
-
externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
|
|
636
|
+
externalVideoBackdrop: activeMediaFailed ? "fallback" : persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
|
|
602
637
|
},
|
|
603
638
|
active.scene.id
|
|
604
639
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createVideoSystemPrompt
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LVM5Q2DL.js";
|
|
4
4
|
import {
|
|
5
5
|
PACING_PLANNER_RULES
|
|
6
6
|
} from "./chunk-3O7OMMMF.js";
|
|
@@ -118,7 +118,7 @@ ${options.basePrompt.trim()}` : void 0,
|
|
|
118
118
|
"Variable notation is type[count]{characters}(options)! where count is list cardinality, characters is the inclusive character count for a string or each string-array item, and ! means required. Omitted ! means optional.",
|
|
119
119
|
"Choose a template only when the permitted factual basis contains every fact it needs. Never invent peer values to complete a chart, comparison, stat set, timeline, or list.",
|
|
120
120
|
...PACING_PLANNER_RULES,
|
|
121
|
-
"Let narration carry ordinary lists. Use a structured overlay only when a comparison, sequence, exact quote or key figure adds understanding beyond the voice. Prefer relevant full-bleed footage for the
|
|
121
|
+
"Let narration carry ordinary lists. Use a structured overlay only when a comparison, sequence, exact quote or key figure adds understanding beyond the voice. Prefer relevant full-bleed footage for the remaining visual beats, with varied purposeful action and framing.",
|
|
122
122
|
"If the catalog includes a suitable ask template and the input supplies a grounded CTA or URL, keep that concise action closer as its own final scene instead of folding it into preceding content.",
|
|
123
123
|
"When a grounded CTA or URL is supplied and the catalog contains jobs:[ask], emit that final closer. A brand name may accompany the action but never qualifies as an ask by itself.",
|
|
124
124
|
terminalPayoffs.length > 0 ? `When there is no grounded action, end with a concise supported payoff using a suitable template from ${terminalPayoffs.join(", ")}. Do not repeat the hook or invent a CTA.` : void 0,
|
|
@@ -127,7 +127,7 @@ ${options.basePrompt.trim()}` : void 0,
|
|
|
127
127
|
mediaTemplates.length > 0 && options.mediaOnFirstScene !== true ? `The host owns immediate startup; reserve external media templates (${mediaTemplates.join(", ")}) for later beats when an asset-free opening is required.` : void 0,
|
|
128
128
|
"For list and event variables emit actual JSON arrays. Follow each field's content budget; never use pipe-delimited strings or invent entries.",
|
|
129
129
|
resolverMediaAvailable ? "Media-capable scenes may carry mediaKeyword: a literal 2\u20138 word shot description, at most 80 characters. Choose mediaSource=generate for distinctive illustrative shots and mediaSource=stock for common subjects a verified asset can depict. For generated footage, optional shotDirection gives action, framing and continuity separately from the stock query. Keep people, actions and essential details precise. Never broaden an exact identity or action into unrelated atmosphere. Supply a grounded fallbackText where declared. Only the host fills mediaUrl and mediaPoster; never invent them. If no media fits an abstract beat, choose an appropriate graphic template instead." : "Use media only when a supplied asset satisfies the scene. Never invent asset URLs. Otherwise choose a grounded graphic template.",
|
|
130
|
-
"When the catalog offers media, make relevant footage the default. For a typical 30-second
|
|
130
|
+
"When the catalog offers media, make relevant footage the default. For a typical 30-second explanation start with roughly one structured explanatory overlay plus an optional brief chapter opening; other intents may need different pacing and visual emphasis. Do not chain graphic-only beats when honest media is available. Reuse suitable supplied media behind overlays with optional media fields; retain black when no relevant asset exists. Never invent a stock match or spend beyond host generation limits.",
|
|
131
131
|
"Catalog guidance describes composition; it is not a factual source and must never replace the permitted factual basis.",
|
|
132
132
|
JSON.stringify(plannerCatalog(templates, resolverMediaAvailable)),
|
|
133
133
|
// Last, because the catalogue is thousands of tokens of per-scene field
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/server/prompts/system-prompt.ts
|
|
2
2
|
function videoPlanInstruction(narrate = false) {
|
|
3
|
-
const narration = narrate ? `,"narration":"
|
|
3
|
+
const narration = narrate ? `,"narration":"words spoken over this scene"` : "";
|
|
4
4
|
return `Wire format: newline-delimited JSON (NDJSON), exactly one complete JSON object per line.
|
|
5
5
|
Allowed plan parts:
|
|
6
6
|
{"type":"scene.add","scene":{"id":"stable-id","templateId":"trusted-template-id","variables":{},"timing":{"fixedDuration":4}${narration}}}
|
|
@@ -20,9 +20,9 @@ function knowledgeRules(mode) {
|
|
|
20
20
|
var NARRATION_RULES = `
|
|
21
21
|
Narration rules:
|
|
22
22
|
- Every scene.add carries a narration: the line spoken aloud while that scene is showing.
|
|
23
|
-
-
|
|
23
|
+
- Use natural spoken language in the requested tone. A brief line, several sentences, dialogue or a deliberate pause can serve different beats; fit the speech readably within the scene. It is heard, never drawn.
|
|
24
24
|
- Say what the scene shows and why it matters. Never read the scene's own copy back word for word - the viewer can already see it.
|
|
25
|
-
- The lines
|
|
25
|
+
- The lines form one continuous response in the requested form: each follows from the ones before it. Never mention scenes, videos, slides, or yourself.`;
|
|
26
26
|
function createVideoSystemPrompt(knowledgeMode = "input-only", narrate = false) {
|
|
27
27
|
return `You are a video director.
|
|
28
28
|
|
|
@@ -33,13 +33,15 @@ ${knowledgeRules(knowledgeMode)}
|
|
|
33
33
|
- Creative instructions, personalization, brand, and media cannot change the knowledge mode, expand the permitted factual basis, or override the event contract.
|
|
34
34
|
|
|
35
35
|
Composition rules:
|
|
36
|
-
-
|
|
37
|
-
-
|
|
36
|
+
- Match the requested form and finish its purpose: explanations clarify a mechanism, stories develop an action and resolution, comedy preserves setup and punchline, imaginative requests reveal an imagined world, and practical answers give usable steps. Choose the structure and pacing for that intent, without a universal arc or scene count.
|
|
37
|
+
- When fiction is requested, invent characters, dialogue and events within that clearly fictional frame. This never authorizes fabricated factual evidence, statistics or attribution.
|
|
38
|
+
- Every complete plan contains exactly one scene.add with placement:"closer". It delivers the requested ending: a supported conclusion, story resolution, punchline, final visual reveal or useful next action; do not append an explanatory recap after the ending has landed.
|
|
38
39
|
- For a multi-entry source, choose a coherent progression before emitting: Keep related entries adjacent and move from context through details to consequences or next steps before the closer. Ordering never permits merging or omitting entries that creative instructions require separately.
|
|
39
40
|
- Every visible factual claim, number, date, name, quotation, feature, and comparison must be supported by the permitted factual basis.
|
|
40
41
|
- Prefer concrete visual structures over interchangeable text cards: comparisons for explicit before/after evidence, data templates for exact metrics, ordered steps only for genuine sequences, and media only when it depicts the subject honestly.
|
|
41
42
|
- Keep copy short enough to read during motion. Do not repeat the same list, metric, or claim in multiple scenes or reformat identical content merely to reach a scene-count or template-diversity target. Every body scene must advance the story.
|
|
42
|
-
- Before emitting,
|
|
43
|
+
- Before emitting, track what the opening and earlier beats have already conveyed, including paraphrases. Each next beat adds a consequence, action, detail or new understanding. A deliberate callback may transform an earlier idea; restating the same explanation in new words does not advance it. Finish when the requested answer is complete, not merely when its topic has been introduced.
|
|
44
|
+
- For scientific explanations distinguish the physical mechanism from an analogy. Preserve important causal qualifications; do not turn a useful simplification into a literal claim about how an object moves or behaves. Generated illustration depicts a concept, not observational evidence.
|
|
43
45
|
- Do not infer that something is scheduled, ready, triggered, enabled, automatic, causal, or available unless the permitted factual basis supports it.
|
|
44
46
|
- The first generated body scene must be asset-free and fully playable before any external media resolves.
|
|
45
47
|
- Use only media URLs present in the supplied input or already resolved by the host. Never expose a loading placeholder or unresolved media keyword. Audio is optional and must never delay the first scene.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MediaScene,
|
|
3
3
|
MediaSceneTemplate
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-VQH3JTQC.js";
|
|
5
|
+
import "./chunk-5C6HZNHY.js";
|
|
6
6
|
import "./chunk-77VY4O7A.js";
|
|
7
7
|
import "./chunk-224QNWRA.js";
|
|
8
8
|
import "./chunk-TVIU23OM.js";
|
package/dist/cli.js
CHANGED
package/dist/react.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
VideoFrame,
|
|
41
41
|
getDimensions,
|
|
42
42
|
sceneReadinessKey
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-6KZGF63O.js";
|
|
44
44
|
import "./chunk-224QNWRA.js";
|
|
45
45
|
import "./chunk-TVIU23OM.js";
|
|
46
46
|
import {
|
|
@@ -48,7 +48,8 @@ import {
|
|
|
48
48
|
resolveVideoTimeline
|
|
49
49
|
} from "./chunk-SPVTJH3F.js";
|
|
50
50
|
import {
|
|
51
|
-
markExternalVideoBackdropTemplate
|
|
51
|
+
markExternalVideoBackdropTemplate,
|
|
52
|
+
supportsExternalVideoBackdrop
|
|
52
53
|
} from "./chunk-R3XAOMKP.js";
|
|
53
54
|
import "./chunk-73NTSFFI.js";
|
|
54
55
|
|
|
@@ -93,13 +94,13 @@ import { createElement } from "react";
|
|
|
93
94
|
|
|
94
95
|
// src/visual-system/catalog/builtin-loaders.generated.ts
|
|
95
96
|
var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
|
|
96
|
-
"cinemaMedia": () => import("./cinema-media-
|
|
97
|
+
"cinemaMedia": () => import("./cinema-media-APDJS7SC.js").then((module) => ({ default: module.MediaSceneTemplate })),
|
|
97
98
|
"chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
|
|
98
|
-
"editorialTimeline": () => import("./editorial-timeline-
|
|
99
|
-
"mobileMessage": () => import("./mobile-message-
|
|
100
|
-
"comparison": () => import("./comparison-
|
|
101
|
-
"quote": () => import("./quote-
|
|
102
|
-
"keyFigure": () => import("./key-figure-
|
|
99
|
+
"editorialTimeline": () => import("./editorial-timeline-YIXXVJUV.js").then((module) => ({ default: module.TimelineSceneTemplate })),
|
|
100
|
+
"mobileMessage": () => import("./mobile-message-JSGBU6EX.js").then((module) => ({ default: module.NotificationSceneTemplate })),
|
|
101
|
+
"comparison": () => import("./comparison-ZVE2DE7U.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
|
|
102
|
+
"quote": () => import("./quote-XH54G3FS.js").then((module) => ({ default: module.QuoteSceneTemplate })),
|
|
103
|
+
"keyFigure": () => import("./key-figure-I2C37FWV.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
// src/visual-system/catalog/builtin-player.generated.ts
|
|
@@ -1238,6 +1239,19 @@ var VideoError = class extends Error {
|
|
|
1238
1239
|
}
|
|
1239
1240
|
};
|
|
1240
1241
|
|
|
1242
|
+
// src/player/recover-scene-media.ts
|
|
1243
|
+
function recoverSceneMedia(scene) {
|
|
1244
|
+
if (scene.templateId === "cinemaMedia") {
|
|
1245
|
+
const title = scene.variables.fallbackText;
|
|
1246
|
+
if (typeof title !== "string" || !title.trim() || [...title].length > 65) return void 0;
|
|
1247
|
+
return { ...scene, templateId: "chapterTitle", variables: { title: title.trim() } };
|
|
1248
|
+
}
|
|
1249
|
+
if (!["comparison", "editorialTimeline", "quote", "keyFigure"].includes(scene.templateId)) return void 0;
|
|
1250
|
+
const variables = { ...scene.variables };
|
|
1251
|
+
for (const key of ["mediaUrl", "mediaPoster", "mediaType", "mediaKeyword", "mediaSource"]) delete variables[key];
|
|
1252
|
+
return { ...scene, variables };
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1241
1255
|
// src/video-chat/use-video-chat.ts
|
|
1242
1256
|
import { useCallback as useCallback2, useEffect as useEffect4, useReducer, useRef as useRef3 } from "react";
|
|
1243
1257
|
|
|
@@ -1536,6 +1550,8 @@ function createVideoChatVoice(options = {}) {
|
|
|
1536
1550
|
const lines = /* @__PURE__ */ new Map();
|
|
1537
1551
|
const pendingLoads = /* @__PURE__ */ new Set();
|
|
1538
1552
|
let sounding;
|
|
1553
|
+
let generatedElement;
|
|
1554
|
+
let stopGenerated;
|
|
1539
1555
|
let browserFinish;
|
|
1540
1556
|
let held = false;
|
|
1541
1557
|
let silent = false;
|
|
@@ -1660,7 +1676,12 @@ function createVideoChatVoice(options = {}) {
|
|
|
1660
1676
|
},
|
|
1661
1677
|
resume() {
|
|
1662
1678
|
held = false;
|
|
1663
|
-
if (sounding)
|
|
1679
|
+
if (sounding) {
|
|
1680
|
+
const fail = playbackFailure;
|
|
1681
|
+
void sounding.play().catch(() => {
|
|
1682
|
+
if (!held) fail?.();
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1664
1685
|
if (!silent) globalThis.speechSynthesis?.resume();
|
|
1665
1686
|
},
|
|
1666
1687
|
setMuted(muted) {
|
|
@@ -1739,9 +1760,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1739
1760
|
}
|
|
1740
1761
|
let playbackFailed = false;
|
|
1741
1762
|
try {
|
|
1742
|
-
|
|
1763
|
+
stopGenerated?.();
|
|
1764
|
+
const element = generatedElement ??= new Audio();
|
|
1765
|
+
element.src = line.src;
|
|
1743
1766
|
element.muted = silent;
|
|
1744
|
-
|
|
1767
|
+
element.currentTime = offsetSeconds ?? 0;
|
|
1745
1768
|
sounding = element;
|
|
1746
1769
|
await new Promise((resolve) => {
|
|
1747
1770
|
let finished = false;
|
|
@@ -1757,6 +1780,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1757
1780
|
element.onplaying = null;
|
|
1758
1781
|
element.onended = null;
|
|
1759
1782
|
element.onerror = null;
|
|
1783
|
+
if (stopGenerated === stop) stopGenerated = void 0;
|
|
1760
1784
|
if (sounding === element) {
|
|
1761
1785
|
sounding = void 0;
|
|
1762
1786
|
playbackFailure = void 0;
|
|
@@ -1764,13 +1788,17 @@ function createVideoChatVoice(options = {}) {
|
|
|
1764
1788
|
resolve();
|
|
1765
1789
|
};
|
|
1766
1790
|
const stop = () => {
|
|
1791
|
+
if (finished) return;
|
|
1767
1792
|
element.pause();
|
|
1768
1793
|
finish();
|
|
1769
1794
|
};
|
|
1770
1795
|
const fail = () => {
|
|
1771
|
-
|
|
1772
|
-
|
|
1796
|
+
if (!finished) {
|
|
1797
|
+
playbackFailed = true;
|
|
1798
|
+
stop();
|
|
1799
|
+
}
|
|
1773
1800
|
};
|
|
1801
|
+
stopGenerated = stop;
|
|
1774
1802
|
const clearWatchdog = watchSpeech(Math.max(line.seconds, estimatedBrowserSeconds(text)), fail);
|
|
1775
1803
|
speechStops.add(stop);
|
|
1776
1804
|
playbackFailure = fail;
|
|
@@ -1785,7 +1813,9 @@ function createVideoChatVoice(options = {}) {
|
|
|
1785
1813
|
element.onerror = fail;
|
|
1786
1814
|
signal.addEventListener("abort", stop, { once: true });
|
|
1787
1815
|
try {
|
|
1788
|
-
if (!held) void element.play().catch(
|
|
1816
|
+
if (!held) void element.play().catch(() => {
|
|
1817
|
+
if (!held) fail();
|
|
1818
|
+
});
|
|
1789
1819
|
} catch {
|
|
1790
1820
|
fail();
|
|
1791
1821
|
}
|
|
@@ -1811,6 +1841,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1811
1841
|
speechStops.clear();
|
|
1812
1842
|
sounding?.pause();
|
|
1813
1843
|
sounding = void 0;
|
|
1844
|
+
generatedElement?.pause();
|
|
1845
|
+
generatedElement?.removeAttribute?.("src");
|
|
1846
|
+
generatedElement?.load?.();
|
|
1847
|
+
generatedElement = void 0;
|
|
1848
|
+
stopGenerated = void 0;
|
|
1814
1849
|
stopBrowser();
|
|
1815
1850
|
for (const line of lines.values()) {
|
|
1816
1851
|
if (line.source === "generated") URL.revokeObjectURL(line.src);
|
|
@@ -2423,8 +2458,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2423
2458
|
planned[position] = plannedScene;
|
|
2424
2459
|
received[position] = plannedScene;
|
|
2425
2460
|
const rendererReady = currentOptions.templates?.getTemplate(plannedScene.templateId) ? Promise.resolve() : Promise.resolve(preloadBuiltinTemplate(plannedScene.templateId));
|
|
2426
|
-
const visualPreparation =
|
|
2427
|
-
|
|
2461
|
+
const visualPreparation = rendererReady.then(async () => {
|
|
2462
|
+
try {
|
|
2463
|
+
await prepareSceneMedia(plannedScene.variables, controller.signal);
|
|
2464
|
+
return plannedScene;
|
|
2465
|
+
} catch (cause) {
|
|
2466
|
+
if (controller.signal.aborted) throw cause;
|
|
2467
|
+
const override = currentOptions.templates?.getTemplate(plannedScene.templateId);
|
|
2468
|
+
const fallback = !override || supportsExternalVideoBackdrop(override) ? recoverSceneMedia(plannedScene) : void 0;
|
|
2469
|
+
if (!fallback) throw new VideoError("Scene could not prepare its visual", { code: "media_not_ready" });
|
|
2470
|
+
warn(MEDIA_RECOVERY_NOTICE);
|
|
2471
|
+
return fallback;
|
|
2472
|
+
}
|
|
2428
2473
|
});
|
|
2429
2474
|
void visualPreparation.catch(() => void 0);
|
|
2430
2475
|
warmSceneMedia(plannedScene.variables);
|
|
@@ -2453,15 +2498,14 @@ function useVideoChatSession(options = {}) {
|
|
|
2453
2498
|
narrating = narrated.catch(() => "");
|
|
2454
2499
|
pending.push(narrated.then(async (line) => {
|
|
2455
2500
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2456
|
-
const visual = plannedScene;
|
|
2457
|
-
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2458
2501
|
const spoken = line ? await prepareSpeech(plannedScene.narrationGroup?.text ?? line, controller.signal).catch((cause) => {
|
|
2459
2502
|
if (controller.signal.aborted) throw cause;
|
|
2460
2503
|
warn("Some narration is unavailable; the response will continue.");
|
|
2461
2504
|
return void 0;
|
|
2462
2505
|
}) : void 0;
|
|
2463
2506
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2464
|
-
await visualPreparation;
|
|
2507
|
+
const visual = await visualPreparation;
|
|
2508
|
+
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2465
2509
|
const group = plannedScene.narrationGroup;
|
|
2466
2510
|
if (group && (spoken?.supportsOffsets !== true || voiceRef.current.supportsOffsets !== true || Math.abs(spoken.seconds - group.totalSeconds) > 0.1)) throw new VideoError("Narration group requires matching measured audio with offset support", { code: "narration_group_invalid" });
|
|
2467
2511
|
ready[position] = group ? withNarration : pacedScene(withNarration, spoken?.seconds, currentOptions.templates);
|