@vanillaskyai/video 0.5.1 → 0.5.3
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 +16 -0
- package/dist/{bg-confetti-KTHLUJPO.js → bg-confetti-572VDSYM.js} +1 -1
- package/dist/{bg-emoji-AW7374DZ.js → bg-emoji-T4IEJ6U2.js} +1 -1
- package/dist/{bg-media-KGYE6UVS.js → bg-media-VSFT5YC7.js} +2 -2
- package/dist/{brand-message-AY43ZQR2.js → brand-message-GSM2LOEP.js} +2 -2
- package/dist/{chart-bar-Z42RDRQT.js → chart-bar-ZGGCV3ED.js} +2 -2
- package/dist/{chart-counter-X7NAOQ2C.js → chart-counter-T3MXDPP6.js} +2 -2
- package/dist/{chart-progress-ring-7NSNNHL4.js → chart-progress-ring-OT5Z2LMK.js} +2 -2
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-3ROGEU5A.js → chunk-3OU7HIEB.js} +232 -174
- package/dist/{chunk-K6YVQODK.js → chunk-65MRKELL.js} +3 -2
- package/dist/{chunk-IJM4B2ZN.js → chunk-MFYFZQHM.js} +1 -1
- package/dist/{chunk-4AODC6MS.js → chunk-SNVWTK63.js} +7 -3
- package/dist/{compose-video-QJHKTOQ7.js → compose-video-JWIEDTEJ.js} +1 -1
- package/dist/{cta-logo-KNBLXUJU.js → cta-logo-CV5NCBHX.js} +2 -2
- package/dist/{cta-media-NY4ASBGG.js → cta-media-TJ7F4GN7.js} +2 -2
- package/dist/{incoming-call-KWOXLGJN.js → incoming-call-FOD6Z4R2.js} +2 -2
- package/dist/{infographic-before-after-OEM4DSTZ.js → infographic-before-after-FQXCQ2CY.js} +1 -1
- package/dist/{infographic-feature-list-ZZ25JRYQ.js → infographic-feature-list-Z37COJMT.js} +2 -2
- package/dist/{infographic-problem-solution-L3RLFDPE.js → infographic-problem-solution-WAJ76HQV.js} +2 -2
- package/dist/{infographic-stat-row-3OL3TNSH.js → infographic-stat-row-ZTS6PO57.js} +2 -2
- package/dist/{infographic-steps-LMBSW22P.js → infographic-steps-SNZCO5ZW.js} +2 -2
- package/dist/{prompt-input-XCQ6TS2L.js → prompt-input-VQ6WUBRH.js} +2 -2
- package/dist/react.js +27 -27
- package/dist/{reaction-YL6YPGPB.js → reaction-QECWNLCQ.js} +2 -2
- package/dist/server.js +1 -1
- package/dist/{showcase-code-EFVHRJUM.js → showcase-code-PVPNSI6U.js} +2 -2
- package/dist/{showcase-phone-CTHXHEBT.js → showcase-phone-G32YB25H.js} +2 -2
- package/dist/{showcase-terminal-XAMPADHN.js → showcase-terminal-PLZTGUL6.js} +2 -2
- package/dist/{showcase-web-LAKHYXSD.js → showcase-web-IRLNVEYD.js} +2 -2
- package/dist/{social-milestone-EOBEN7CX.js → social-milestone-T67PZ5RR.js} +2 -2
- package/dist/{social-notification-MELRWIB6.js → social-notification-P7ZSTEZ5.js} +2 -2
- package/dist/{social-review-stack-H3U437VT.js → social-review-stack-IXAC23MY.js} +2 -2
- package/dist/{social-testimonial-GNNMKMDD.js → social-testimonial-AGSFR2IF.js} +2 -2
- package/dist/{social-tweet-PBUKKPT3.js → social-tweet-UXTQA5VW.js} +2 -2
- package/dist/test.js +1 -1
- package/docs/motion-and-effects.md +12 -2
- package/examples/nextjs-quickstart/package.json +1 -1
- package/package.json +1 -1
- package/registry/items/backgrounds.json +1 -1
- package/registry/items/barChart.json +1 -1
- package/registry/items/bigNumber.json +1 -1
- package/registry/items/brandMessage.json +1 -1
- package/registry/items/cardList.json +1 -1
- package/registry/items/codeEditor.json +1 -1
- package/registry/items/ctaLogo.json +1 -1
- package/registry/items/ctaMedia.json +1 -1
- package/registry/items/incomingCall.json +1 -1
- package/registry/items/media.json +1 -1
- package/registry/items/milestone.json +1 -1
- package/registry/items/notification.json +1 -1
- package/registry/items/phoneMockup.json +1 -1
- package/registry/items/problemSolution.json +1 -1
- package/registry/items/progressRing.json +1 -1
- package/registry/items/promptInput.json +1 -1
- package/registry/items/reaction.json +1 -1
- package/registry/items/reviewStack.json +1 -1
- package/registry/items/steps.json +1 -1
- package/registry/items/terminal.json +1 -1
- package/registry/items/testimonial.json +1 -1
- package/registry/items/tripleStats.json +1 -1
- package/registry/items/tweet.json +1 -1
- package/registry/items/webMockup.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.5.3
|
|
8
|
+
|
|
9
|
+
- Makes `crossfade` the default for generated videos and mounts changed media
|
|
10
|
+
invisibly before every contiguous cut, including templates that own their
|
|
11
|
+
transition. This extends the existing media warm-up window so the browser can
|
|
12
|
+
decode the incoming backdrop and replace its stock poster with the real first
|
|
13
|
+
frame while still hidden, instead of flashing the gradient or visibly swapping
|
|
14
|
+
crops after the cut. Making camera motion opt-in also removes the scale reset
|
|
15
|
+
that could look like a quick pullback during the fade.
|
|
16
|
+
|
|
17
|
+
## 0.5.2
|
|
18
|
+
|
|
19
|
+
- Refactors composition state transitions into a deterministic, directly
|
|
20
|
+
tested session engine without changing the public API, event stream,
|
|
21
|
+
checksums, or runtime behavior.
|
|
22
|
+
|
|
7
23
|
## 0.5.1
|
|
8
24
|
|
|
9
25
|
- Removes the redundant README version badge. Package registries and release
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SceneBackground,
|
|
3
3
|
getMediaBackgroundProps
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SNVWTK63.js";
|
|
5
5
|
import {
|
|
6
6
|
hasSceneMedia
|
|
7
7
|
} from "./chunk-35LIYILY.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
TemplateText
|
|
14
14
|
} from "./chunk-TCEHK2BW.js";
|
|
15
15
|
import "./chunk-L5BTTCZV.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-MFYFZQHM.js";
|
|
17
17
|
import "./chunk-XTOXFMLM.js";
|
|
18
18
|
import {
|
|
19
19
|
resolveTokens
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SceneBackground,
|
|
6
6
|
getMediaBackgroundProps
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SNVWTK63.js";
|
|
8
8
|
import "./chunk-35LIYILY.js";
|
|
9
9
|
import {
|
|
10
10
|
renderWithEmoji
|
|
11
11
|
} from "./chunk-L5BTTCZV.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-MFYFZQHM.js";
|
|
13
13
|
import {
|
|
14
14
|
interpolate
|
|
15
15
|
} from "./chunk-XTOXFMLM.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SceneBackground,
|
|
3
3
|
getMediaBackgroundProps
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SNVWTK63.js";
|
|
5
5
|
import {
|
|
6
6
|
hasSceneMedia
|
|
7
7
|
} from "./chunk-35LIYILY.js";
|
|
@@ -12,7 +12,7 @@ import "./chunk-L5BTTCZV.js";
|
|
|
12
12
|
import {
|
|
13
13
|
TOP_TEXT_AREA_RATIO,
|
|
14
14
|
withOpacity
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MFYFZQHM.js";
|
|
16
16
|
import {
|
|
17
17
|
SPRING_SNAPPY,
|
|
18
18
|
interpolate,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SceneBackground,
|
|
6
6
|
getMediaBackgroundProps
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SNVWTK63.js";
|
|
8
8
|
import {
|
|
9
9
|
hasSceneMedia
|
|
10
10
|
} from "./chunk-35LIYILY.js";
|
|
@@ -19,7 +19,7 @@ import "./chunk-L5BTTCZV.js";
|
|
|
19
19
|
import {
|
|
20
20
|
TOP_TEXT_AREA_RATIO,
|
|
21
21
|
withOpacity
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-MFYFZQHM.js";
|
|
23
23
|
import {
|
|
24
24
|
interpolate,
|
|
25
25
|
spring
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
SceneBackground,
|
|
7
7
|
getMediaBackgroundProps
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SNVWTK63.js";
|
|
9
9
|
import {
|
|
10
10
|
hasSceneMedia
|
|
11
11
|
} from "./chunk-35LIYILY.js";
|
|
@@ -16,7 +16,7 @@ import "./chunk-L5BTTCZV.js";
|
|
|
16
16
|
import {
|
|
17
17
|
TOP_TEXT_AREA_RATIO,
|
|
18
18
|
withOpacity
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-MFYFZQHM.js";
|
|
20
20
|
import {
|
|
21
21
|
interpolate
|
|
22
22
|
} from "./chunk-XTOXFMLM.js";
|
package/dist/check-runtime.js
CHANGED
|
@@ -223,8 +223,9 @@ function buildInitialComposition(input, audio, snapshotRetention, closerReserveS
|
|
|
223
223
|
brand: resolveVideoBrand(input.brand),
|
|
224
224
|
density: input.style?.density ?? "normal",
|
|
225
225
|
motion: input.style?.motion ?? "normal",
|
|
226
|
-
defaultBackgroundEffect: input.style?.backgroundEffect ?? "
|
|
227
|
-
defaultTextArchetype: input.style?.textArchetype ?? "subtle"
|
|
226
|
+
defaultBackgroundEffect: input.style?.backgroundEffect ?? "static",
|
|
227
|
+
defaultTextArchetype: input.style?.textArchetype ?? "subtle",
|
|
228
|
+
defaultTransition: "crossfade"
|
|
228
229
|
},
|
|
229
230
|
meta
|
|
230
231
|
},
|
|
@@ -349,6 +350,193 @@ function createSceneQualityWarnings(scene) {
|
|
|
349
350
|
}];
|
|
350
351
|
}
|
|
351
352
|
|
|
353
|
+
// src/server/composition-session.ts
|
|
354
|
+
var warningActions = (warnings) => warnings.map((warning) => ({ type: "response.warning", warning }));
|
|
355
|
+
function createCompositionSession() {
|
|
356
|
+
return {
|
|
357
|
+
acceptedSceneCount: 0,
|
|
358
|
+
rejectedSceneCount: 0,
|
|
359
|
+
planCompleted: false,
|
|
360
|
+
runtimeDurationLimited: false,
|
|
361
|
+
deferredForCloser: [],
|
|
362
|
+
closerCommitted: false,
|
|
363
|
+
plannerReportedLength: false,
|
|
364
|
+
finishReason: "stop"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function pace(scene, video, options, closerReserveSec) {
|
|
368
|
+
return paceScene(scene, {
|
|
369
|
+
previousScenes: video.scenes,
|
|
370
|
+
audio: video.audio,
|
|
371
|
+
maxDurationSec: options.maxDurationSec,
|
|
372
|
+
closerReserveSec,
|
|
373
|
+
getTemplatePacing: options.getTemplatePacing
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function commitScene(session, result, includeQualityWarnings) {
|
|
377
|
+
return {
|
|
378
|
+
session: {
|
|
379
|
+
...session,
|
|
380
|
+
acceptedSceneCount: session.acceptedSceneCount + 1
|
|
381
|
+
},
|
|
382
|
+
actions: [
|
|
383
|
+
...warningActions(result.warnings),
|
|
384
|
+
...includeQualityWarnings ? warningActions(createSceneQualityWarnings(result.scene)) : [],
|
|
385
|
+
{ type: "scene.add", scene: result.scene }
|
|
386
|
+
]
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function completePlan(session, reportedFinishReason, requireCloser) {
|
|
390
|
+
const missingCloser = requireCloser && !session.closerCommitted;
|
|
391
|
+
const finishReason = session.runtimeDurationLimited ? "length" : reportedFinishReason != null && reportedFinishReason !== "stop" ? reportedFinishReason : missingCloser ? "other" : "stop";
|
|
392
|
+
return {
|
|
393
|
+
session: {
|
|
394
|
+
...session,
|
|
395
|
+
planCompleted: true,
|
|
396
|
+
plannerReportedLength: reportedFinishReason === "length",
|
|
397
|
+
finishReason
|
|
398
|
+
},
|
|
399
|
+
actions: missingCloser ? [{ type: "response.warning", warning: createMissingCloserWarning() }] : []
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function advanceComposition(session, part, video, options) {
|
|
403
|
+
if (session.planCompleted) {
|
|
404
|
+
throw new Error(part.type === "plan.complete" ? "The planner emitted plan.complete more than once" : "The planner emitted content after plan.complete");
|
|
405
|
+
}
|
|
406
|
+
if (part.type === "plan.complete") {
|
|
407
|
+
if (!session.pendingCloser) {
|
|
408
|
+
let next2 = { ...session, deferredForCloser: [] };
|
|
409
|
+
let currentVideo = video;
|
|
410
|
+
const actions2 = [];
|
|
411
|
+
for (const deferred of session.deferredForCloser) {
|
|
412
|
+
const recovered = pace(deferred, currentVideo, options, 0);
|
|
413
|
+
if (!recovered.scene) {
|
|
414
|
+
next2 = {
|
|
415
|
+
...next2,
|
|
416
|
+
rejectedSceneCount: next2.rejectedSceneCount + 1,
|
|
417
|
+
runtimeDurationLimited: true
|
|
418
|
+
};
|
|
419
|
+
actions2.push(...warningActions(recovered.warnings));
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const committed2 = commitScene(next2, { ...recovered, scene: recovered.scene }, true);
|
|
423
|
+
next2 = committed2.session;
|
|
424
|
+
actions2.push(...committed2.actions);
|
|
425
|
+
currentVideo = { ...currentVideo, scenes: [...currentVideo.scenes, recovered.scene] };
|
|
426
|
+
}
|
|
427
|
+
const completed2 = completePlan(next2, part.finishReason, options.requireCloser);
|
|
428
|
+
return {
|
|
429
|
+
session: completed2.session,
|
|
430
|
+
actions: [...actions2, ...completed2.actions]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
let next = {
|
|
434
|
+
...session,
|
|
435
|
+
rejectedSceneCount: session.rejectedSceneCount + session.deferredForCloser.length,
|
|
436
|
+
runtimeDurationLimited: session.runtimeDurationLimited || session.deferredForCloser.length > 0,
|
|
437
|
+
deferredForCloser: []
|
|
438
|
+
};
|
|
439
|
+
const actions = session.deferredForCloser.map((scene) => ({
|
|
440
|
+
type: "response.warning",
|
|
441
|
+
warning: createOmittedForCloserWarning(scene.id)
|
|
442
|
+
}));
|
|
443
|
+
const closer = pace(session.pendingCloser, video, options, 0);
|
|
444
|
+
if (!closer.scene) {
|
|
445
|
+
next = {
|
|
446
|
+
...next,
|
|
447
|
+
rejectedSceneCount: next.rejectedSceneCount + 1,
|
|
448
|
+
runtimeDurationLimited: true
|
|
449
|
+
};
|
|
450
|
+
actions.push(...warningActions(closer.warnings));
|
|
451
|
+
const completed2 = completePlan(next, part.finishReason, options.requireCloser);
|
|
452
|
+
return {
|
|
453
|
+
session: completed2.session,
|
|
454
|
+
actions: [...actions, ...completed2.actions]
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const committed = commitScene(next, { ...closer, scene: closer.scene }, false);
|
|
458
|
+
next = { ...committed.session, closerCommitted: true };
|
|
459
|
+
const completed = completePlan(next, part.finishReason, options.requireCloser);
|
|
460
|
+
return {
|
|
461
|
+
session: completed.session,
|
|
462
|
+
actions: [...actions, ...committed.actions, ...completed.actions]
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const metadata = options.getTemplatePacing?.(part.scene.templateId);
|
|
466
|
+
const isAsk = metadata?.jobs?.includes("ask") === true;
|
|
467
|
+
const isPayoff = metadata?.jobs?.includes("payoff") === true;
|
|
468
|
+
if (part.placement === "closer" && metadata && !isAsk && !isPayoff) {
|
|
469
|
+
throw new Error(`Scene template ${part.scene.templateId} cannot be used as a closer`);
|
|
470
|
+
}
|
|
471
|
+
if (part.placement === "closer" || isAsk) {
|
|
472
|
+
if (session.pendingCloser) {
|
|
473
|
+
return {
|
|
474
|
+
session: {
|
|
475
|
+
...session,
|
|
476
|
+
rejectedSceneCount: session.rejectedSceneCount + 1
|
|
477
|
+
},
|
|
478
|
+
actions: [{
|
|
479
|
+
type: "response.warning",
|
|
480
|
+
warning: createDuplicateCloserWarning()
|
|
481
|
+
}]
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
return { session: { ...session, pendingCloser: part.scene }, actions: [] };
|
|
485
|
+
}
|
|
486
|
+
if (session.deferredForCloser.length > 0) {
|
|
487
|
+
return {
|
|
488
|
+
session: { ...session, deferredForCloser: [...session.deferredForCloser, part.scene] },
|
|
489
|
+
actions: []
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
const paced = pace(
|
|
493
|
+
part.scene,
|
|
494
|
+
video,
|
|
495
|
+
options,
|
|
496
|
+
session.pendingCloser ? pendingCloserReserve(session.pendingCloser, options.getTemplatePacing) : options.closerReserveSec
|
|
497
|
+
);
|
|
498
|
+
if (!paced.scene) {
|
|
499
|
+
if (paced.warnings.some(({ code }) => code === "scene_omitted_for_closer")) {
|
|
500
|
+
return {
|
|
501
|
+
session: { ...session, deferredForCloser: [...session.deferredForCloser, part.scene] },
|
|
502
|
+
actions: []
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
session: {
|
|
507
|
+
...session,
|
|
508
|
+
rejectedSceneCount: session.rejectedSceneCount + 1,
|
|
509
|
+
runtimeDurationLimited: true
|
|
510
|
+
},
|
|
511
|
+
actions: warningActions(paced.warnings)
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
return commitScene(session, { ...paced, scene: paced.scene }, true);
|
|
515
|
+
}
|
|
516
|
+
function rejectCompositionScene(session) {
|
|
517
|
+
return {
|
|
518
|
+
...session,
|
|
519
|
+
rejectedSceneCount: session.rejectedSceneCount + 1
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function recoverComposition(session, video, options) {
|
|
523
|
+
if (!session.pendingCloser || session.closerCommitted || video.scenes.length === 0) {
|
|
524
|
+
return { session, actions: [] };
|
|
525
|
+
}
|
|
526
|
+
const closer = pace(session.pendingCloser, video, options, 0);
|
|
527
|
+
if (!closer.scene) {
|
|
528
|
+
return {
|
|
529
|
+
session,
|
|
530
|
+
actions: warningActions(closer.warnings)
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const committed = commitScene(session, { ...closer, scene: closer.scene }, false);
|
|
534
|
+
return {
|
|
535
|
+
session: { ...committed.session, closerCommitted: true },
|
|
536
|
+
actions: committed.actions
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
352
540
|
// src/server/composition-runtime.ts
|
|
353
541
|
function invokeIsolated(callback, value) {
|
|
354
542
|
if (!callback) return;
|
|
@@ -422,8 +610,6 @@ function createVideo(rawInput, options) {
|
|
|
422
610
|
rejectResult = reject;
|
|
423
611
|
});
|
|
424
612
|
const startedAt = monotonicNow();
|
|
425
|
-
let acceptedSceneCount = 0;
|
|
426
|
-
let rejectedSceneCount = 0;
|
|
427
613
|
let timeToFirstSceneMs;
|
|
428
614
|
const { sink: lifecycle, settle: settleProviderLifecycle } = createProviderLifecycle();
|
|
429
615
|
const reportedErrors = /* @__PURE__ */ new WeakSet();
|
|
@@ -434,6 +620,13 @@ function createVideo(rawInput, options) {
|
|
|
434
620
|
};
|
|
435
621
|
const eventSource = (async function* () {
|
|
436
622
|
let state = createVideoState();
|
|
623
|
+
let composition = createCompositionSession();
|
|
624
|
+
const compositionOptions = {
|
|
625
|
+
maxDurationSec: input.maxDurationSec ?? 30,
|
|
626
|
+
closerReserveSec,
|
|
627
|
+
requireCloser: options.requireCloser ?? false,
|
|
628
|
+
getTemplatePacing: options.getTemplatePacing
|
|
629
|
+
};
|
|
437
630
|
const emit = (event) => {
|
|
438
631
|
state = applyVideoEvent(state, event);
|
|
439
632
|
if (event.type === "response.warning") {
|
|
@@ -442,21 +635,23 @@ function createVideo(rawInput, options) {
|
|
|
442
635
|
options.onEvent?.(event);
|
|
443
636
|
return event;
|
|
444
637
|
};
|
|
638
|
+
const emitCompositionAction = (action) => {
|
|
639
|
+
if (action.type === "response.warning") {
|
|
640
|
+
return emit(events.create("response.warning", { warning: action.warning }));
|
|
641
|
+
}
|
|
642
|
+
const event = emit(events.create("scene.add", {
|
|
643
|
+
scene: action.scene,
|
|
644
|
+
position: state.config?.scenes.length ?? 0
|
|
645
|
+
}));
|
|
646
|
+
timeToFirstSceneMs ??= Math.max(0, monotonicNow() - startedAt);
|
|
647
|
+
return event;
|
|
648
|
+
};
|
|
445
649
|
const finish = (finalState) => {
|
|
446
650
|
if (!settled) {
|
|
447
651
|
settled = true;
|
|
448
652
|
resolveResult(finalState);
|
|
449
653
|
}
|
|
450
654
|
};
|
|
451
|
-
let planCompleted = false;
|
|
452
|
-
let generatedSceneCount = 0;
|
|
453
|
-
let runtimeDurationLimited = false;
|
|
454
|
-
let pendingCloser;
|
|
455
|
-
const deferredForCloser = [];
|
|
456
|
-
let plannerReportedLength = false;
|
|
457
|
-
let closerCommitted = false;
|
|
458
|
-
let missingCloser = false;
|
|
459
|
-
let finishReason = "stop";
|
|
460
655
|
try {
|
|
461
656
|
yield emit(events.create("response.start", {
|
|
462
657
|
requestId,
|
|
@@ -484,8 +679,8 @@ function createVideo(rawInput, options) {
|
|
|
484
679
|
const part = resolveSuppliedMediaPlanPart(parseVideoPlanPart(untrustedPart), input);
|
|
485
680
|
attemptedScene = part.type === "scene.add";
|
|
486
681
|
if (controller.signal.aborted) throw controller.signal.reason ?? new Error(abortReason);
|
|
487
|
-
if (planCompleted
|
|
488
|
-
throw new Error("The planner emitted content after plan.complete");
|
|
682
|
+
if (composition.planCompleted) {
|
|
683
|
+
throw new Error(part.type === "plan.complete" ? "The planner emitted plan.complete more than once" : "The planner emitted content after plan.complete");
|
|
489
684
|
}
|
|
490
685
|
if (part.type === "scene.add") {
|
|
491
686
|
if (options.capabilities?.templates != null && !options.capabilities.templates.includes(part.scene.templateId)) {
|
|
@@ -495,138 +690,16 @@ function createVideo(rawInput, options) {
|
|
|
495
690
|
input,
|
|
496
691
|
previousScenes: state.config?.scenes ?? []
|
|
497
692
|
});
|
|
498
|
-
const templatePacing = options.getTemplatePacing?.(part.scene.templateId);
|
|
499
|
-
const isAskCandidate = templatePacing?.jobs?.includes("ask") === true;
|
|
500
|
-
const isPayoffCandidate = templatePacing?.jobs?.includes("payoff") === true;
|
|
501
|
-
if (part.placement === "closer" && templatePacing && !isAskCandidate && !isPayoffCandidate) {
|
|
502
|
-
throw new Error(`Scene template ${part.scene.templateId} cannot be used as a closer`);
|
|
503
|
-
}
|
|
504
|
-
const isCloserCandidate = part.placement === "closer" || isAskCandidate;
|
|
505
|
-
if (isCloserCandidate) {
|
|
506
|
-
if (pendingCloser) {
|
|
507
|
-
rejectedSceneCount += 1;
|
|
508
|
-
yield emit(events.create("response.warning", {
|
|
509
|
-
warning: createDuplicateCloserWarning()
|
|
510
|
-
}));
|
|
511
|
-
} else {
|
|
512
|
-
pendingCloser = part.scene;
|
|
513
|
-
}
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
if (deferredForCloser.length) {
|
|
517
|
-
deferredForCloser.push(part.scene);
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
const paced = paceScene(part.scene, {
|
|
521
|
-
previousScenes: state.config?.scenes ?? [],
|
|
522
|
-
audio: state.config?.audio,
|
|
523
|
-
maxDurationSec: input.maxDurationSec ?? 30,
|
|
524
|
-
closerReserveSec: pendingCloser ? pendingCloserReserve(pendingCloser, options.getTemplatePacing) : closerReserveSec,
|
|
525
|
-
getTemplatePacing: options.getTemplatePacing
|
|
526
|
-
});
|
|
527
|
-
if (!paced.scene) {
|
|
528
|
-
if (paced.warnings.some(({ code }) => code === "scene_omitted_for_closer")) {
|
|
529
|
-
deferredForCloser.push(part.scene);
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
for (const warning of paced.warnings) {
|
|
533
|
-
yield emit(events.create("response.warning", { warning }));
|
|
534
|
-
}
|
|
535
|
-
runtimeDurationLimited = true;
|
|
536
|
-
rejectedSceneCount += 1;
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
const scene = paced.scene;
|
|
540
|
-
for (const warning of paced.warnings) {
|
|
541
|
-
yield emit(events.create("response.warning", { warning }));
|
|
542
|
-
}
|
|
543
|
-
for (const warning of createSceneQualityWarnings(scene)) {
|
|
544
|
-
yield emit(events.create("response.warning", { warning }));
|
|
545
|
-
}
|
|
546
|
-
yield emit(events.create("scene.add", {
|
|
547
|
-
scene,
|
|
548
|
-
position: state.config?.scenes.length ?? 0
|
|
549
|
-
}));
|
|
550
|
-
generatedSceneCount += 1;
|
|
551
|
-
acceptedSceneCount += 1;
|
|
552
|
-
timeToFirstSceneMs ??= Math.max(0, monotonicNow() - startedAt);
|
|
553
|
-
} else if (part.type === "plan.complete") {
|
|
554
|
-
if (planCompleted) throw new Error("The planner emitted plan.complete more than once");
|
|
555
|
-
if (pendingCloser) {
|
|
556
|
-
if (deferredForCloser.length) runtimeDurationLimited = true;
|
|
557
|
-
for (const deferred of deferredForCloser.splice(0)) {
|
|
558
|
-
rejectedSceneCount += 1;
|
|
559
|
-
yield emit(events.create("response.warning", {
|
|
560
|
-
warning: createOmittedForCloserWarning(deferred.id)
|
|
561
|
-
}));
|
|
562
|
-
}
|
|
563
|
-
const pacedCloser = paceScene(pendingCloser, {
|
|
564
|
-
previousScenes: state.config?.scenes ?? [],
|
|
565
|
-
audio: state.config?.audio,
|
|
566
|
-
maxDurationSec: input.maxDurationSec ?? 30,
|
|
567
|
-
closerReserveSec: 0,
|
|
568
|
-
getTemplatePacing: options.getTemplatePacing
|
|
569
|
-
});
|
|
570
|
-
for (const warning of pacedCloser.warnings) {
|
|
571
|
-
yield emit(events.create("response.warning", { warning }));
|
|
572
|
-
}
|
|
573
|
-
if (pacedCloser.scene) {
|
|
574
|
-
yield emit(events.create("scene.add", {
|
|
575
|
-
scene: pacedCloser.scene,
|
|
576
|
-
position: state.config?.scenes.length ?? 0
|
|
577
|
-
}));
|
|
578
|
-
generatedSceneCount += 1;
|
|
579
|
-
acceptedSceneCount += 1;
|
|
580
|
-
timeToFirstSceneMs ??= Math.max(0, monotonicNow() - startedAt);
|
|
581
|
-
closerCommitted = true;
|
|
582
|
-
} else {
|
|
583
|
-
runtimeDurationLimited = true;
|
|
584
|
-
rejectedSceneCount += 1;
|
|
585
|
-
}
|
|
586
|
-
} else if (deferredForCloser.length) {
|
|
587
|
-
for (const deferred of deferredForCloser.splice(0)) {
|
|
588
|
-
const recovered = paceScene(deferred, {
|
|
589
|
-
previousScenes: state.config?.scenes ?? [],
|
|
590
|
-
audio: state.config?.audio,
|
|
591
|
-
maxDurationSec: input.maxDurationSec ?? 30,
|
|
592
|
-
closerReserveSec: 0,
|
|
593
|
-
getTemplatePacing: options.getTemplatePacing
|
|
594
|
-
});
|
|
595
|
-
for (const warning of recovered.warnings) {
|
|
596
|
-
yield emit(events.create("response.warning", { warning }));
|
|
597
|
-
}
|
|
598
|
-
if (!recovered.scene) {
|
|
599
|
-
runtimeDurationLimited = true;
|
|
600
|
-
rejectedSceneCount += 1;
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
for (const warning of createSceneQualityWarnings(recovered.scene)) {
|
|
604
|
-
yield emit(events.create("response.warning", { warning }));
|
|
605
|
-
}
|
|
606
|
-
yield emit(events.create("scene.add", {
|
|
607
|
-
scene: recovered.scene,
|
|
608
|
-
position: state.config?.scenes.length ?? 0
|
|
609
|
-
}));
|
|
610
|
-
generatedSceneCount += 1;
|
|
611
|
-
acceptedSceneCount += 1;
|
|
612
|
-
timeToFirstSceneMs ??= Math.max(0, monotonicNow() - startedAt);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
if (options.requireCloser && !closerCommitted) {
|
|
616
|
-
missingCloser = true;
|
|
617
|
-
yield emit(events.create("response.warning", {
|
|
618
|
-
warning: createMissingCloserWarning()
|
|
619
|
-
}));
|
|
620
|
-
}
|
|
621
|
-
planCompleted = true;
|
|
622
|
-
const reportedFinishReason = part.finishReason ?? "stop";
|
|
623
|
-
finishReason = runtimeDurationLimited ? "length" : reportedFinishReason !== "stop" ? reportedFinishReason : missingCloser ? "other" : "stop";
|
|
624
|
-
plannerReportedLength = part.finishReason === "length";
|
|
625
|
-
continue;
|
|
626
693
|
}
|
|
694
|
+
if (!state.config) throw new Error("response.start did not initialize composition state");
|
|
695
|
+
const transition = advanceComposition(composition, part, state.config, compositionOptions);
|
|
696
|
+
for (const action of transition.actions) yield emitCompositionAction(action);
|
|
697
|
+
composition = transition.session;
|
|
627
698
|
} catch (cause) {
|
|
628
699
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
629
|
-
if (attemptedScene || untrustedPart != null && typeof untrustedPart === "object" && untrustedPart.type === "scene.add")
|
|
700
|
+
if (attemptedScene || untrustedPart != null && typeof untrustedPart === "object" && untrustedPart.type === "scene.add") {
|
|
701
|
+
composition = rejectCompositionScene(composition);
|
|
702
|
+
}
|
|
630
703
|
reportError(error);
|
|
631
704
|
if ((options.invalidPartBehavior ?? "fail") === "fail") throw error;
|
|
632
705
|
yield emit(events.create("response.error", {
|
|
@@ -646,35 +719,35 @@ function createVideo(rawInput, options) {
|
|
|
646
719
|
for (const warning of provider.warnings) {
|
|
647
720
|
yield emit(events.create("response.warning", { warning }));
|
|
648
721
|
}
|
|
649
|
-
if (!planCompleted) throw new Error("The planner stream ended before plan.complete");
|
|
650
|
-
if (plannerReportedLength &&
|
|
722
|
+
if (!composition.planCompleted) throw new Error("The planner stream ended before plan.complete");
|
|
723
|
+
if (composition.plannerReportedLength && composition.acceptedSceneCount === 0) {
|
|
651
724
|
throw new Error("The planner was truncated before adding a generated scene");
|
|
652
725
|
}
|
|
653
|
-
if (plannerReportedLength) {
|
|
726
|
+
if (composition.plannerReportedLength) {
|
|
654
727
|
yield emit(events.create("response.warning", {
|
|
655
728
|
warning: createIncompletePlanWarning()
|
|
656
729
|
}));
|
|
657
730
|
}
|
|
658
|
-
if (
|
|
731
|
+
if (composition.acceptedSceneCount === 0 && requiresGeneratedScene) {
|
|
659
732
|
throw new Error("The planner completed without adding a scene");
|
|
660
733
|
}
|
|
661
734
|
const snapshot = parseVideo(state.config);
|
|
662
735
|
const completeEvent = emit(events.create("response.complete", {
|
|
663
|
-
finishReason,
|
|
736
|
+
finishReason: composition.finishReason,
|
|
664
737
|
snapshot,
|
|
665
738
|
checksum: checksumVideo(snapshot)
|
|
666
739
|
}));
|
|
667
740
|
finish(state);
|
|
668
741
|
const summary = {
|
|
669
|
-
finishReason,
|
|
742
|
+
finishReason: composition.finishReason,
|
|
670
743
|
...provider.usage ? { usage: provider.usage } : {},
|
|
671
744
|
...provider.providerMetadata !== void 0 ? { providerMetadata: provider.providerMetadata } : {},
|
|
672
745
|
...provider.requestedModelId ? { requestedModelId: provider.requestedModelId } : {},
|
|
673
746
|
...provider.resolvedModelId ? { resolvedModelId: provider.resolvedModelId } : {},
|
|
674
747
|
...timeToFirstSceneMs != null ? { timeToFirstSceneMs } : {},
|
|
675
748
|
totalDurationMs: Math.max(0, monotonicNow() - startedAt),
|
|
676
|
-
acceptedSceneCount,
|
|
677
|
-
rejectedSceneCount,
|
|
749
|
+
acceptedSceneCount: composition.acceptedSceneCount,
|
|
750
|
+
rejectedSceneCount: composition.rejectedSceneCount,
|
|
678
751
|
videoDurationSec: snapshot.scenes.at(-1)?.timing.endTime ?? 0,
|
|
679
752
|
warnings: state.warnings.map(cloneWarning)
|
|
680
753
|
};
|
|
@@ -691,30 +764,16 @@ function createVideo(rawInput, options) {
|
|
|
691
764
|
return;
|
|
692
765
|
}
|
|
693
766
|
try {
|
|
694
|
-
if (
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
maxDurationSec: input.maxDurationSec ?? 30,
|
|
699
|
-
closerReserveSec: 0,
|
|
700
|
-
getTemplatePacing: options.getTemplatePacing
|
|
701
|
-
});
|
|
702
|
-
for (const warning of pacedCloser.warnings) {
|
|
703
|
-
yield emit(events.create("response.warning", { warning }));
|
|
704
|
-
}
|
|
705
|
-
if (pacedCloser.scene) {
|
|
706
|
-
yield emit(events.create("scene.add", {
|
|
707
|
-
scene: pacedCloser.scene,
|
|
708
|
-
position: state.config.scenes.length
|
|
709
|
-
}));
|
|
710
|
-
generatedSceneCount += 1;
|
|
711
|
-
acceptedSceneCount += 1;
|
|
712
|
-
closerCommitted = true;
|
|
713
|
-
}
|
|
767
|
+
if (state.config) {
|
|
768
|
+
const recovery = recoverComposition(composition, state.config, compositionOptions);
|
|
769
|
+
for (const action of recovery.actions) yield emitCompositionAction(action);
|
|
770
|
+
composition = recovery.session;
|
|
714
771
|
}
|
|
715
772
|
const provider = await settleProviderLifecycle();
|
|
716
773
|
for (const warning of provider.warnings) {
|
|
717
|
-
if (!state.warnings.some(
|
|
774
|
+
if (!state.warnings.some(
|
|
775
|
+
(existing) => existing.code === warning.code && existing.message === warning.message
|
|
776
|
+
)) {
|
|
718
777
|
yield emit(events.create("response.warning", { warning }));
|
|
719
778
|
}
|
|
720
779
|
}
|
|
@@ -752,7 +811,6 @@ function createVideo(rawInput, options) {
|
|
|
752
811
|
return {
|
|
753
812
|
request,
|
|
754
813
|
initialConfig,
|
|
755
|
-
initialStyle: initialConfig.style,
|
|
756
814
|
stream,
|
|
757
815
|
result,
|
|
758
816
|
abort(reason = "user cancelled") {
|
|
@@ -173,11 +173,12 @@ function VideoFrame({
|
|
|
173
173
|
const next = activeIndex < timeline.length - 1 ? timeline[activeIndex + 1] : void 0;
|
|
174
174
|
const activeTemplate = kit.getTemplate(active.scene.templateId);
|
|
175
175
|
const nextTemplate = next ? kit.getTemplate(next.scene.templateId) : void 0;
|
|
176
|
-
const contiguousNext = next && rangesAreContiguous(active, next)
|
|
176
|
+
const contiguousNext = next && rangesAreContiguous(active, next) ? next : void 0;
|
|
177
|
+
const transitionableNext = contiguousNext && activeTemplate.usesGlobalTransition && activeTemplate.transitionTiming && nextTemplate?.usesGlobalTransition && nextTemplate.transitionTiming ? contiguousNext : void 0;
|
|
177
178
|
const activeTiming = activeTemplate.transitionTiming;
|
|
178
179
|
const nextTiming = nextTemplate?.transitionTiming;
|
|
179
180
|
const eligibleNextTransition = Boolean(
|
|
180
|
-
transitionEnabled &&
|
|
181
|
+
transitionEnabled && transitionableNext && activeTiming && nextTiming && sceneBackgroundChanges(active, transitionableNext)
|
|
181
182
|
);
|
|
182
183
|
const blendDuration = Math.min(
|
|
183
184
|
SCENE_TRANSITION_SECONDS,
|
|
@@ -147,7 +147,7 @@ function getDriftTransform(progress, direction) {
|
|
|
147
147
|
}
|
|
148
148
|
function getBackgroundTransform(effect, progress, beatIntensity = 0, sceneIndex = 0) {
|
|
149
149
|
const dir = DIRECTIONS[sceneIndex % DIRECTIONS.length];
|
|
150
|
-
const resolved = effect ?? "
|
|
150
|
+
const resolved = effect ?? "static";
|
|
151
151
|
switch (resolved) {
|
|
152
152
|
case "slow-zoom-in":
|
|
153
153
|
return {
|