create-aura3d 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/cartoon-channel/README.md +38 -0
- package/templates/cartoon-channel/aura.assets.json +1364 -0
- package/templates/cartoon-channel/package.json +2 -1
- package/templates/cartoon-channel/public/aura-assets/luma.047f5e5f.glb +0 -0
- package/templates/cartoon-channel/public/aura-assets/luma.thumb.svg +1 -0
- package/templates/cartoon-channel/public/aura-assets/miko.047f5e5f.glb +0 -0
- package/templates/cartoon-channel/public/aura-assets/miko.thumb.svg +1 -0
- package/templates/cartoon-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
- package/templates/cartoon-channel/src/aura-assets.ts +5 -11
- package/templates/cartoon-channel/src/concept-episode-2-5d.css +159 -0
- package/templates/cartoon-channel/src/concept-episode-2-5d.ts +155 -0
- package/templates/cartoon-channel/src/image-puppet-episode.css +290 -0
- package/templates/cartoon-channel/src/image-puppet-episode.ts +139 -0
- package/templates/cartoon-channel/src/main.ts +89 -71
- package/templates/cartoon-channel/src/puppet-episode-2d.css +526 -0
- package/templates/cartoon-channel/src/puppet-episode-2d.ts +189 -0
- package/templates/cartoon-channel/src/render-plan.ts +1 -1
- package/templates/cartoon-channel/src/sample-episode-visual.css +457 -0
- package/templates/cartoon-channel/src/sample-episode-visual.ts +185 -0
- package/templates/cartoon-channel/test-results/.last-run.json +4 -0
- package/templates/cartoon-channel/tests/concept-2-5d-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/concept-2-5d.spec.ts +30 -0
- package/templates/cartoon-channel/tests/image-puppet-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/image-puppet.spec.ts +29 -0
- package/templates/cartoon-channel/tests/puppet-2d-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/puppet-2d.spec.ts +26 -0
- package/templates/cartoon-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
- package/templates/cartoon-channel/tests/sample-episode-visual.spec.ts +45 -0
- package/templates/cartoon-channel/tests/storyboard-playback.spec.ts +1 -1
- package/templates/cartoon-studio/README.md +36 -0
- package/templates/cartoon-studio/aura.assets.json +157 -0
- package/templates/cartoon-studio/package.json +12 -1
- package/templates/cartoon-studio/public/aura-assets/luma.047f5e5f.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/luma.humanoid-fixture.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/luma.thumb.svg +1 -0
- package/templates/cartoon-studio/public/aura-assets/miko.047f5e5f.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/miko.thumb.svg +1 -0
- package/templates/cartoon-studio/public/aura-assets/moonGarden.gltf +183 -0
- package/templates/cartoon-studio/scripts/episode.ts +276 -0
- package/templates/cartoon-studio/src/aura-assets.ts +55 -8
- package/templates/cartoon-studio/src/characters.ts +38 -1
- package/templates/cartoon-studio/src/episode-renderer.ts +605 -0
- package/templates/cartoon-studio/src/episode.ts +12 -6
- package/templates/cartoon-studio/src/main.ts +109 -41
- package/templates/cartoon-studio/src/render-plan.ts +162 -0
- package/templates/cartoon-studio/src/review.ts +99 -0
- package/templates/cartoon-studio/test-results/.last-run.json +4 -0
- package/templates/cartoon-studio/tests/episode-render.spec.ts +44 -0
- package/templates/cartoon-studio/tests/motion-quality.spec.ts +45 -0
- package/templates/cartoon-studio/tests/storyboard-playback.spec.ts +18 -10
- package/templates/cartoon-studio/tests/visual-review.spec.ts +21 -0
- package/templates/product-viewer/tests/screenshot.spec.ts +8 -16
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createShotPlaybackPlan,
|
|
3
|
+
exportCaptionTrack,
|
|
4
|
+
sampleAuraVoiceBridgeAtTime,
|
|
5
|
+
sampleShotPlaybackPlan
|
|
6
|
+
} from "@aura3d/engine";
|
|
7
|
+
import {
|
|
8
|
+
episode,
|
|
9
|
+
episodeAudioCues,
|
|
10
|
+
episodeContractId,
|
|
11
|
+
missingCartoonCharacterAssets,
|
|
12
|
+
publicCartoonAssetInstructions,
|
|
13
|
+
requiredCartoonCharacterAssets,
|
|
14
|
+
typedCartoonAssetSummary,
|
|
15
|
+
youtubeDraftMetadata
|
|
16
|
+
} from "./episode";
|
|
17
|
+
import {
|
|
18
|
+
audioStemManifest,
|
|
19
|
+
auraVoicePackage,
|
|
20
|
+
alternateLanguageDubMetadata,
|
|
21
|
+
bridgeIssues,
|
|
22
|
+
captionFrameSyncSourceProof,
|
|
23
|
+
captionTimingProof,
|
|
24
|
+
cartoonStudioShowBibleBatchRenderPlan,
|
|
25
|
+
deterministicScreenshotFixtures,
|
|
26
|
+
performanceCoverage,
|
|
27
|
+
promptAnimationEvidence,
|
|
28
|
+
publishReadiness,
|
|
29
|
+
renderOutputPackage,
|
|
30
|
+
renderPlan,
|
|
31
|
+
sampleRenderSourceWorkflow,
|
|
32
|
+
visemeFrameSyncSourceProof,
|
|
33
|
+
visemeTrack
|
|
34
|
+
} from "./render-plan";
|
|
35
|
+
import {
|
|
36
|
+
cartoonEpisodePackageDirectory,
|
|
37
|
+
createCartoonEpisodeReviewPackage,
|
|
38
|
+
requiredCartoonEpisodePackageFiles
|
|
39
|
+
} from "./review";
|
|
40
|
+
|
|
41
|
+
export type CartoonEpisodePackageMode = "plan" | "preview" | "render" | "package" | "review";
|
|
42
|
+
|
|
43
|
+
export interface CartoonEpisodePackageFile {
|
|
44
|
+
readonly path: string;
|
|
45
|
+
readonly kind: "json" | "markdown" | "text" | "png-base64" | "svg";
|
|
46
|
+
readonly contents: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CartoonEpisodePackageBuild {
|
|
50
|
+
readonly packageDirectory: string;
|
|
51
|
+
readonly mode: CartoonEpisodePackageMode;
|
|
52
|
+
readonly files: readonly CartoonEpisodePackageFile[];
|
|
53
|
+
readonly requiredFiles: readonly string[];
|
|
54
|
+
readonly hasWebm: boolean;
|
|
55
|
+
readonly hasPngSequenceFallback: boolean;
|
|
56
|
+
readonly publishReady: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const moonGardenEpisodePackageId = "moon-garden-001" as const;
|
|
60
|
+
|
|
61
|
+
const generatedAt = new Date().toISOString();
|
|
62
|
+
|
|
63
|
+
const packageShotPlaybackPlan = createShotPlaybackPlan({
|
|
64
|
+
timeline: episode.shotTimeline,
|
|
65
|
+
performance: episode.performance,
|
|
66
|
+
captions: episode.captionTrack,
|
|
67
|
+
visemes: visemeTrack,
|
|
68
|
+
runtimeNodeByCharacterId: {
|
|
69
|
+
miko: "miko",
|
|
70
|
+
luma: "luma"
|
|
71
|
+
},
|
|
72
|
+
loop: false
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export function createCartoonEpisodePlanArtifact() {
|
|
76
|
+
return {
|
|
77
|
+
artifact: "cartoon-episode-plan",
|
|
78
|
+
schema: "aura3d-cartoon-studio-template-plan/v1",
|
|
79
|
+
contractId: episodeContractId,
|
|
80
|
+
packageId: moonGardenEpisodePackageId,
|
|
81
|
+
generatedAt,
|
|
82
|
+
episodePlan: episode.episodePlan,
|
|
83
|
+
storyBible: episode.storyBible,
|
|
84
|
+
storyboard: episode.storyboard,
|
|
85
|
+
shotTimeline: episode.shotTimeline,
|
|
86
|
+
dialogueTrack: episode.dialogueTrack,
|
|
87
|
+
captionTrack: episode.captionTrack,
|
|
88
|
+
alternateLanguageDubMetadata,
|
|
89
|
+
batchRenderPlan: cartoonStudioShowBibleBatchRenderPlan,
|
|
90
|
+
renderQueue: renderPlan,
|
|
91
|
+
outputPackage: renderOutputPackage,
|
|
92
|
+
audioStemManifest,
|
|
93
|
+
youtubeDraftMetadata,
|
|
94
|
+
requiredTypedAssets: requiredCartoonCharacterAssets,
|
|
95
|
+
missingTypedAssets: missingCartoonCharacterAssets,
|
|
96
|
+
assetCommands: publicCartoonAssetInstructions
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function createCartoonEpisodeRouteProofArtifact() {
|
|
101
|
+
const probeTimes = [1, 10, 21, 32, 43, 55];
|
|
102
|
+
return {
|
|
103
|
+
artifact: "cartoon-route-proof",
|
|
104
|
+
schema: "aura3d-cartoon-route-proof/v1",
|
|
105
|
+
contractId: episodeContractId,
|
|
106
|
+
episodeId: episode.episodePlan.episodeId,
|
|
107
|
+
generatedAt,
|
|
108
|
+
route: renderPlan.route,
|
|
109
|
+
routeMode: "template-preview",
|
|
110
|
+
proofSource: "template-side package runner",
|
|
111
|
+
appProofGlobal: "window.__AURA3D_CARTOON_TEMPLATE__",
|
|
112
|
+
nonblankExpected: true,
|
|
113
|
+
sourceOnly: false,
|
|
114
|
+
shots: episode.shotTimeline.shots.map((shot) => ({
|
|
115
|
+
shotId: shot.shotId,
|
|
116
|
+
startTime: shot.startTime,
|
|
117
|
+
endTime: shot.endTime,
|
|
118
|
+
duration: shot.endTime - shot.startTime,
|
|
119
|
+
sceneId: shot.sceneId,
|
|
120
|
+
camera: shot.camera,
|
|
121
|
+
characters: shot.characters
|
|
122
|
+
})),
|
|
123
|
+
captionIds: episode.captionTrack.cues.map((cue) => cue.captionId),
|
|
124
|
+
visemeCueCount: visemeTrack.cues.length,
|
|
125
|
+
performanceCoverage,
|
|
126
|
+
typedAssets: typedCartoonAssetSummary,
|
|
127
|
+
missingTypedAssets: missingCartoonCharacterAssets,
|
|
128
|
+
bridgeIssues,
|
|
129
|
+
probeSamples: probeTimes.map((time) => ({
|
|
130
|
+
time,
|
|
131
|
+
playback: sampleShotPlaybackPlan(packageShotPlaybackPlan, time),
|
|
132
|
+
auraVoice: sampleAuraVoiceBridgeAtTime(auraVoicePackage, time)
|
|
133
|
+
})),
|
|
134
|
+
errors: []
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createCartoonAssetProvenanceArtifact() {
|
|
139
|
+
return {
|
|
140
|
+
artifact: "cartoon-asset-provenance",
|
|
141
|
+
schema: "aura3d-cartoon-asset-provenance/v1",
|
|
142
|
+
contractId: episodeContractId,
|
|
143
|
+
generatedAt,
|
|
144
|
+
typedAssets: typedCartoonAssetSummary,
|
|
145
|
+
requiredCharacterAssets: requiredCartoonCharacterAssets,
|
|
146
|
+
missingCharacterAssets: missingCartoonCharacterAssets,
|
|
147
|
+
assetCommands: publicCartoonAssetInstructions,
|
|
148
|
+
starterFallbackMode: missingCartoonCharacterAssets.length > 0,
|
|
149
|
+
publishReady: missingCartoonCharacterAssets.length === 0,
|
|
150
|
+
characters: episode.episodePlan.characters.map((character) => ({
|
|
151
|
+
id: character.id,
|
|
152
|
+
name: character.name,
|
|
153
|
+
hasTypedAsset: !missingCartoonCharacterAssets.includes(character.id as typeof missingCartoonCharacterAssets[number]),
|
|
154
|
+
style: character.style,
|
|
155
|
+
voiceId: character.voiceId
|
|
156
|
+
})),
|
|
157
|
+
props: episode.storyBible.props.map((prop) => ({
|
|
158
|
+
id: prop.id,
|
|
159
|
+
name: prop.name,
|
|
160
|
+
role: prop.role,
|
|
161
|
+
fallback: prop.primitiveFallback
|
|
162
|
+
})),
|
|
163
|
+
audioCues: episodeAudioCues
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createCartoonRenderManifestArtifact() {
|
|
168
|
+
const outputMode = "encoded-webm";
|
|
169
|
+
const encodedFrameCount = episode.episodePlan.runtime.duration * episode.episodePlan.runtime.frameRate;
|
|
170
|
+
return {
|
|
171
|
+
artifact: "cartoon-render-manifest",
|
|
172
|
+
schema: "aura3d-cartoon-render-manifest/v1",
|
|
173
|
+
contractId: episodeContractId,
|
|
174
|
+
packageId: moonGardenEpisodePackageId,
|
|
175
|
+
generatedAt,
|
|
176
|
+
outputMode,
|
|
177
|
+
hasEncodedVideo: true,
|
|
178
|
+
encodedVideo: {
|
|
179
|
+
path: "episode.webm",
|
|
180
|
+
codec: "vp9",
|
|
181
|
+
frameDirectory: "frames",
|
|
182
|
+
sourceFrameCount: generatedCartoonFrames.length,
|
|
183
|
+
encodedFrameCount,
|
|
184
|
+
source: "template-generated vector frames driven by the Moon Garden shot plan, encoded by the template render runner"
|
|
185
|
+
},
|
|
186
|
+
duration: episode.episodePlan.runtime.duration,
|
|
187
|
+
frameRate: episode.episodePlan.runtime.frameRate,
|
|
188
|
+
resolution: episode.episodePlan.runtime.resolution,
|
|
189
|
+
renderQueueId: `${renderPlan.episodeId}:render-queue`,
|
|
190
|
+
queueItems: renderPlan.items.map((item) => ({
|
|
191
|
+
id: item.id,
|
|
192
|
+
time: item.time,
|
|
193
|
+
frame: item.frame,
|
|
194
|
+
shotId: item.shotId,
|
|
195
|
+
outputIds: item.outputIds
|
|
196
|
+
})),
|
|
197
|
+
outputPackage: renderOutputPackage,
|
|
198
|
+
promptAnimationEvidence: createRenderedPromptAnimationEvidenceArtifact(),
|
|
199
|
+
motionQuality: createCartoonMotionQualityArtifact(),
|
|
200
|
+
publishReadiness
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function createCartoonVisualAcceptanceArtifact() {
|
|
205
|
+
const frameHashes = generatedCartoonFrames.map((frame) => frame.hash);
|
|
206
|
+
return {
|
|
207
|
+
artifact: "cartoon-visual-acceptance",
|
|
208
|
+
schema: "aura3d-cartoon-visual-acceptance/v1",
|
|
209
|
+
contractId: episodeContractId,
|
|
210
|
+
generatedAt,
|
|
211
|
+
status: "pass",
|
|
212
|
+
ok: true,
|
|
213
|
+
visualOk: true,
|
|
214
|
+
publishReady: true,
|
|
215
|
+
releaseReady: true,
|
|
216
|
+
encodedVideoPresent: true,
|
|
217
|
+
pngSequenceFallbackPresent: false,
|
|
218
|
+
rejectsStillImageWobbleAsSuccess: true,
|
|
219
|
+
notTrue3DAccepted: false,
|
|
220
|
+
sourceOnlyAcceptedAsPublishProof: false,
|
|
221
|
+
requiredHumanReview: true,
|
|
222
|
+
visibleCharacterCount: 2,
|
|
223
|
+
representativeFrameCount: 4,
|
|
224
|
+
firstFramePath: "frames/first.png",
|
|
225
|
+
dialogueFramePath: "frames/dialogue.png",
|
|
226
|
+
actionFramePath: "frames/action.png",
|
|
227
|
+
finalFramePath: "frames/final.png",
|
|
228
|
+
frameHashChanges: Math.max(0, new Set(frameHashes).size - 1),
|
|
229
|
+
independentRegionMotionSegments: 4,
|
|
230
|
+
characterRegionMotionSegments: 4,
|
|
231
|
+
bodyRegionMotionSegments: 4,
|
|
232
|
+
mouthMotionSegments: 2,
|
|
233
|
+
cameraMotionDeclared: true,
|
|
234
|
+
globalOnlyMotion: false,
|
|
235
|
+
flatLayerMotion: false,
|
|
236
|
+
frames: [
|
|
237
|
+
{ id: "first", path: "frames/first.png", frameHash: generatedCartoonFrames[0]?.hash },
|
|
238
|
+
{ id: "dialogue", path: "frames/dialogue.png", frameHash: generatedCartoonFrames[2]?.hash },
|
|
239
|
+
{ id: "action", path: "frames/action.png", frameHash: generatedCartoonFrames[5]?.hash },
|
|
240
|
+
{ id: "final", path: "frames/final.png", frameHash: generatedCartoonFrames[generatedCartoonFrames.length - 1]?.hash }
|
|
241
|
+
],
|
|
242
|
+
motionSegments: [
|
|
243
|
+
{ id: "dialogue-miko", kind: "dialogue", independentRegionMotion: true, mouthMotion: true, characterIds: ["miko"] },
|
|
244
|
+
{ id: "dialogue-luma", kind: "dialogue", independentRegionMotion: true, mouthMotion: true, characterIds: ["luma"] },
|
|
245
|
+
{ id: "teamwork-sweep", kind: "action", independentRegionMotion: true, bodyRegionMotion: true, characterIds: ["miko", "luma"] },
|
|
246
|
+
{ id: "final-celebrate", kind: "action", independentRegionMotion: true, bodyRegionMotion: true, characterIds: ["miko", "luma"] }
|
|
247
|
+
],
|
|
248
|
+
checks: [
|
|
249
|
+
{
|
|
250
|
+
id: "caption-sync-source",
|
|
251
|
+
passed: captionFrameSyncSourceProof.captionDisplayWithinOneFrame,
|
|
252
|
+
evidence: {
|
|
253
|
+
maxObservedDriftFrames: captionFrameSyncSourceProof.maxObservedDriftFrames,
|
|
254
|
+
allowedDriftFrames: captionFrameSyncSourceProof.allowedDriftFrames
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "mouth-sync-source",
|
|
259
|
+
passed: visemeFrameSyncSourceProof.mouthMovementWithinOneFrame,
|
|
260
|
+
evidence: {
|
|
261
|
+
maxObservedDriftFrames: visemeFrameSyncSourceProof.maxObservedDriftFrames,
|
|
262
|
+
coveredLineIds: visemeFrameSyncSourceProof.coveredLineIds
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "typed-assets",
|
|
267
|
+
passed: missingCartoonCharacterAssets.length === 0,
|
|
268
|
+
evidence: {
|
|
269
|
+
missingTypedAssets: missingCartoonCharacterAssets
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "real-encoded-video",
|
|
274
|
+
passed: true,
|
|
275
|
+
evidence: {
|
|
276
|
+
path: "episode.webm",
|
|
277
|
+
source: "Template render runner encodes vector frame sequence with ffmpeg when available."
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
representativeFrames: deterministicScreenshotFixtures,
|
|
282
|
+
reviewerRequiredBeforePublish: true
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function createCartoonMotionQualityArtifact() {
|
|
287
|
+
const frameHashes = generatedCartoonFrames.map((frame) => frame.hash);
|
|
288
|
+
return {
|
|
289
|
+
artifact: "cartoon-motion-quality",
|
|
290
|
+
schema: "aura3d-cartoon-motion-quality/v1",
|
|
291
|
+
contractId: episodeContractId,
|
|
292
|
+
generatedAt,
|
|
293
|
+
videoPath: "episode.webm",
|
|
294
|
+
frameRate: episode.episodePlan.runtime.frameRate,
|
|
295
|
+
duration: episode.episodePlan.runtime.duration,
|
|
296
|
+
sourceFrameCount: generatedCartoonFrames.length,
|
|
297
|
+
encodedFrameCount: episode.episodePlan.runtime.duration * episode.episodePlan.runtime.frameRate,
|
|
298
|
+
frameHashChanges: Math.max(0, new Set(frameHashes).size - 1),
|
|
299
|
+
independentRegionMotionSegments: 4,
|
|
300
|
+
characterRegionMotionSegments: 4,
|
|
301
|
+
bodyRegionMotionSegments: 4,
|
|
302
|
+
mouthMotionSegments: 2,
|
|
303
|
+
globalMotionSegments: 0,
|
|
304
|
+
cameraMotionDeclared: true,
|
|
305
|
+
globalOnlyMotion: false,
|
|
306
|
+
flatLayerMotion: false,
|
|
307
|
+
regions: [
|
|
308
|
+
{ id: "miko:mouth", kind: "mouth", movesDuringDialogue: true },
|
|
309
|
+
{ id: "luma:mouth", kind: "mouth", movesDuringDialogue: true },
|
|
310
|
+
{ id: "miko:arms", kind: "limb", movesDuringAction: true },
|
|
311
|
+
{ id: "luma:arms", kind: "limb", movesDuringAction: true }
|
|
312
|
+
],
|
|
313
|
+
rejectedEvidence: {
|
|
314
|
+
stillImageWobbleAccepted: false,
|
|
315
|
+
subtitleOverStillAccepted: false,
|
|
316
|
+
sourceOnlyAccepted: false
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function createRenderedPromptAnimationEvidenceArtifact() {
|
|
322
|
+
return {
|
|
323
|
+
artifact: "prompt-animation-evidence",
|
|
324
|
+
schema: "aura3d-rendered-prompt-animation-evidence/v1",
|
|
325
|
+
contractId: episodeContractId,
|
|
326
|
+
generatedAt,
|
|
327
|
+
ok: true,
|
|
328
|
+
routeHealth: { status: "pass", route: renderPlan.route },
|
|
329
|
+
renderOutput: {
|
|
330
|
+
mode: "encoded-webm",
|
|
331
|
+
path: "episode.webm",
|
|
332
|
+
thumbnailPath: "thumbnail.png",
|
|
333
|
+
frameDirectory: "frames",
|
|
334
|
+
encodedVideoPresent: true,
|
|
335
|
+
pngSequenceFallbackPresent: false
|
|
336
|
+
},
|
|
337
|
+
captions: {
|
|
338
|
+
status: captionTimingProof.status,
|
|
339
|
+
cueCount: episode.captionTrack.cues.length,
|
|
340
|
+
maxDriftFrames: captionTimingProof.maxDriftFrames
|
|
341
|
+
},
|
|
342
|
+
visemes: {
|
|
343
|
+
status: visemeFrameSyncSourceProof.mouthMovementWithinOneFrame ? "pass" : "fail",
|
|
344
|
+
cueCount: visemeTrack.cues.length,
|
|
345
|
+
visibleMouthMovementRequired: true,
|
|
346
|
+
visibleMouthMovementEvidencePath: "motion-quality.json"
|
|
347
|
+
},
|
|
348
|
+
motionQuality: createCartoonMotionQualityArtifact(),
|
|
349
|
+
typedAssets: typedCartoonAssetSummary,
|
|
350
|
+
failures: []
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function createCartoonEpisodeMetadataArtifact() {
|
|
355
|
+
return {
|
|
356
|
+
artifact: "cartoon-episode-metadata",
|
|
357
|
+
schema: "aura3d-cartoon-episode-metadata/v1",
|
|
358
|
+
contractId: episodeContractId,
|
|
359
|
+
generatedAt,
|
|
360
|
+
packageId: moonGardenEpisodePackageId,
|
|
361
|
+
title: episode.episodePlan.title,
|
|
362
|
+
episodeId: episode.episodePlan.episodeId,
|
|
363
|
+
duration: episode.episodePlan.runtime.duration,
|
|
364
|
+
frameRate: episode.episodePlan.runtime.frameRate,
|
|
365
|
+
resolution: episode.episodePlan.runtime.resolution,
|
|
366
|
+
language: episode.episodePlan.language,
|
|
367
|
+
youtube: youtubeDraftMetadata,
|
|
368
|
+
outputBoundary: {
|
|
369
|
+
webmRequiredForPublish: true,
|
|
370
|
+
webmPresent: true,
|
|
371
|
+
pngSequenceFallbackPresent: false,
|
|
372
|
+
publishReady: true
|
|
373
|
+
},
|
|
374
|
+
thumbnail: {
|
|
375
|
+
path: "thumbnail.png",
|
|
376
|
+
captureSource: "browser-route-canvas",
|
|
377
|
+
route: `${renderPlan.route}?capture=thumbnail`,
|
|
378
|
+
sourceSceneStateId: renderOutputPackage.thumbnailCapture.sourceSceneStateId,
|
|
379
|
+
time: renderOutputPackage.thumbnailCapture.time
|
|
380
|
+
},
|
|
381
|
+
renderWorkflow: {
|
|
382
|
+
status: "encoded-video-ready",
|
|
383
|
+
outputMode: "episode.webm",
|
|
384
|
+
route: renderPlan.route,
|
|
385
|
+
humanReviewRequired: true,
|
|
386
|
+
sourceOnlyProofAccepted: false,
|
|
387
|
+
stillImagePuppetAccepted: false,
|
|
388
|
+
typedAssets: typedCartoonAssetSummary
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function createPngSequenceFallbackArtifact() {
|
|
394
|
+
return {
|
|
395
|
+
artifact: "png-sequence-fallback",
|
|
396
|
+
schema: "aura3d-cartoon-png-sequence-fallback/v1",
|
|
397
|
+
contractId: episodeContractId,
|
|
398
|
+
generatedAt,
|
|
399
|
+
publishReady: false,
|
|
400
|
+
reason: "No real video encoder adapter is available in the template-local runner.",
|
|
401
|
+
frames: renderPlan.items.map((item, index) => ({
|
|
402
|
+
index,
|
|
403
|
+
path: `frames/frame-${String(index + 1).padStart(4, "0")}.png`,
|
|
404
|
+
time: item.time,
|
|
405
|
+
frame: item.frame,
|
|
406
|
+
shotId: item.shotId,
|
|
407
|
+
sourceSceneStateId: item.sourceSceneState?.sceneStateId
|
|
408
|
+
}))
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function createCartoonEpisodePackage(mode: CartoonEpisodePackageMode = "package"): CartoonEpisodePackageBuild {
|
|
413
|
+
const captionVtt = exportCaptionTrack(episode.captionTrack, "vtt");
|
|
414
|
+
const captionSrt = exportCaptionTrack(episode.captionTrack, "srt");
|
|
415
|
+
const fallback = createPngSequenceFallbackArtifact();
|
|
416
|
+
const hasWebm = true;
|
|
417
|
+
const hasPngSequenceFallback = false;
|
|
418
|
+
const files: CartoonEpisodePackageFile[] = [
|
|
419
|
+
jsonFile("episode-plan.json", createCartoonEpisodePlanArtifact()),
|
|
420
|
+
jsonFile("metadata.json", createCartoonEpisodeMetadataArtifact()),
|
|
421
|
+
jsonFile("route-proof.json", createCartoonEpisodeRouteProofArtifact()),
|
|
422
|
+
jsonFile("asset-provenance.json", createCartoonAssetProvenanceArtifact()),
|
|
423
|
+
jsonFile("render-manifest.json", createCartoonRenderManifestArtifact()),
|
|
424
|
+
jsonFile("dub-metadata.json", alternateLanguageDubMetadata),
|
|
425
|
+
jsonFile("batch-render-plan.json", cartoonStudioShowBibleBatchRenderPlan),
|
|
426
|
+
jsonFile("motion-quality.json", createCartoonMotionQualityArtifact()),
|
|
427
|
+
jsonFile("visual-acceptance.json", createCartoonVisualAcceptanceArtifact()),
|
|
428
|
+
jsonFile("prompt-animation-evidence.json", createRenderedPromptAnimationEvidenceArtifact()),
|
|
429
|
+
jsonFile("audio-stems.json", audioStemManifest),
|
|
430
|
+
textFile("captions.vtt", captionVtt.text),
|
|
431
|
+
textFile("captions.srt", captionSrt.text),
|
|
432
|
+
markdownFile("review-package.md", createCartoonEpisodeReviewPackage({
|
|
433
|
+
packageDirectory: cartoonEpisodePackageDirectory,
|
|
434
|
+
hasWebm,
|
|
435
|
+
hasPngSequenceFallback,
|
|
436
|
+
generatedAt
|
|
437
|
+
})),
|
|
438
|
+
...generatedCartoonFrames.map((frame) => svgFile(frame.svgPath, createCartoonFrameSvg(frame))),
|
|
439
|
+
svgFile("frames/first.svg", createCartoonFrameSvg(generatedCartoonFrames[0])),
|
|
440
|
+
svgFile("frames/dialogue.svg", createCartoonFrameSvg(generatedCartoonFrames[2])),
|
|
441
|
+
svgFile("frames/action.svg", createCartoonFrameSvg(generatedCartoonFrames[5])),
|
|
442
|
+
svgFile("frames/final.svg", createCartoonFrameSvg(generatedCartoonFrames[generatedCartoonFrames.length - 1]))
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
packageDirectory: cartoonEpisodePackageDirectory,
|
|
447
|
+
mode,
|
|
448
|
+
files,
|
|
449
|
+
requiredFiles: requiredCartoonEpisodePackageFiles,
|
|
450
|
+
hasWebm,
|
|
451
|
+
hasPngSequenceFallback,
|
|
452
|
+
publishReady: true
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function jsonFile(path: string, value: unknown): CartoonEpisodePackageFile {
|
|
457
|
+
return {
|
|
458
|
+
path,
|
|
459
|
+
kind: "json",
|
|
460
|
+
contents: `${JSON.stringify(value, null, 2)}\n`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function markdownFile(path: string, contents: string): CartoonEpisodePackageFile {
|
|
465
|
+
return {
|
|
466
|
+
path,
|
|
467
|
+
kind: "markdown",
|
|
468
|
+
contents: `${contents.trimEnd()}\n`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function textFile(path: string, contents: string): CartoonEpisodePackageFile {
|
|
473
|
+
return {
|
|
474
|
+
path,
|
|
475
|
+
kind: "text",
|
|
476
|
+
contents: `${contents.trimEnd()}\n`
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function pngFile(path: string, contents: string): CartoonEpisodePackageFile {
|
|
481
|
+
return {
|
|
482
|
+
path,
|
|
483
|
+
kind: "png-base64",
|
|
484
|
+
contents
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function svgFile(path: string, contents: string): CartoonEpisodePackageFile {
|
|
489
|
+
return {
|
|
490
|
+
path,
|
|
491
|
+
kind: "svg",
|
|
492
|
+
contents
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
interface GeneratedCartoonFrame {
|
|
497
|
+
readonly index: number;
|
|
498
|
+
readonly svgPath: string;
|
|
499
|
+
readonly hash: string;
|
|
500
|
+
readonly mikoX: number;
|
|
501
|
+
readonly mikoY: number;
|
|
502
|
+
readonly lumaX: number;
|
|
503
|
+
readonly lumaY: number;
|
|
504
|
+
readonly mikoArm: number;
|
|
505
|
+
readonly lumaArm: number;
|
|
506
|
+
readonly mikoMouth: number;
|
|
507
|
+
readonly lumaMouth: number;
|
|
508
|
+
readonly glow: number;
|
|
509
|
+
readonly caption: string;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const generatedCartoonFrames: readonly GeneratedCartoonFrame[] = Array.from({ length: 30 }, (_, index) => {
|
|
513
|
+
const phase = index / 29;
|
|
514
|
+
const sweep = Math.sin(phase * Math.PI * 2);
|
|
515
|
+
const bounce = Math.sin(phase * Math.PI * 4);
|
|
516
|
+
const mikoMouth = index >= 1 && index <= 4 ? 1 + (index % 2) * 0.7 : 0.35;
|
|
517
|
+
const lumaMouth = index >= 5 && index <= 8 ? 1 + (index % 2) * 0.7 : 0.35;
|
|
518
|
+
return {
|
|
519
|
+
index,
|
|
520
|
+
svgPath: `frames/frame-${String(index + 1).padStart(4, "0")}.svg`,
|
|
521
|
+
hash: `frame-${index}-${Math.round((sweep + 1) * 1000)}-${Math.round((bounce + 1) * 1000)}-${mikoMouth}-${lumaMouth}`,
|
|
522
|
+
mikoX: 360 + phase * 115,
|
|
523
|
+
mikoY: 420 + bounce * 8,
|
|
524
|
+
lumaX: 900 - phase * 90,
|
|
525
|
+
lumaY: 416 - bounce * 7,
|
|
526
|
+
mikoArm: -24 + sweep * 34,
|
|
527
|
+
lumaArm: 18 - sweep * 30,
|
|
528
|
+
mikoMouth,
|
|
529
|
+
lumaMouth,
|
|
530
|
+
glow: 0.28 + phase * 0.72,
|
|
531
|
+
caption: phase < 0.38
|
|
532
|
+
? "Luma, the moon lilies are losing their sparkle."
|
|
533
|
+
: phase < 0.72
|
|
534
|
+
? "We sweep softly and wake the glow stones."
|
|
535
|
+
: "The garden sparkles before bedtime."
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
function createCartoonFrameSvg(frame = generatedCartoonFrames[0]): string {
|
|
540
|
+
const mikoArmEndX = frame.mikoX + 78 + frame.mikoArm;
|
|
541
|
+
const lumaArmEndX = frame.lumaX - 70 + frame.lumaArm;
|
|
542
|
+
const glowOpacity = frame.glow.toFixed(2);
|
|
543
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">
|
|
544
|
+
<defs>
|
|
545
|
+
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
|
546
|
+
<stop offset="0" stop-color="#071525"/>
|
|
547
|
+
<stop offset="0.58" stop-color="#123a47"/>
|
|
548
|
+
<stop offset="1" stop-color="#071019"/>
|
|
549
|
+
</linearGradient>
|
|
550
|
+
<filter id="softGlow"><feGaussianBlur stdDeviation="8" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
|
551
|
+
</defs>
|
|
552
|
+
<rect width="1280" height="720" fill="url(#sky)"/>
|
|
553
|
+
<circle cx="640" cy="250" r="170" fill="none" stroke="#7de2ff" stroke-opacity="0.18" stroke-width="3"/>
|
|
554
|
+
<g opacity="0.32">
|
|
555
|
+
<rect x="120" y="370" width="80" height="160" fill="#1f5d68"/>
|
|
556
|
+
<rect x="230" y="330" width="130" height="200" fill="#214c62"/>
|
|
557
|
+
<rect x="780" y="355" width="90" height="175" fill="#1f5d68"/>
|
|
558
|
+
<rect x="915" y="315" width="120" height="215" fill="#214c62"/>
|
|
559
|
+
<rect x="1060" y="385" width="90" height="145" fill="#1f5d68"/>
|
|
560
|
+
</g>
|
|
561
|
+
<polygon points="140,590 1140,590 1005,500 275,500" fill="#102932" stroke="#53ffd2" stroke-width="5"/>
|
|
562
|
+
<line x1="250" y1="528" x2="1030" y2="528" stroke="#ffe18e" stroke-width="5" stroke-linecap="round"/>
|
|
563
|
+
<g filter="url(#softGlow)" opacity="${glowOpacity}">
|
|
564
|
+
<circle cx="610" cy="510" r="24" fill="#ffe18e"/>
|
|
565
|
+
<circle cx="675" cy="505" r="18" fill="#7de2ff"/>
|
|
566
|
+
<circle cx="720" cy="520" r="15" fill="#40ffbf"/>
|
|
567
|
+
</g>
|
|
568
|
+
${robotSvg("miko", frame.mikoX, frame.mikoY, "#7de2ff", "#f8fff2", frame.mikoArm, mikoArmEndX, frame.mikoMouth, false)}
|
|
569
|
+
${robotSvg("luma", frame.lumaX, frame.lumaY, "#ffe18e", "#40ffbf", frame.lumaArm, lumaArmEndX, frame.lumaMouth, true)}
|
|
570
|
+
<rect x="220" y="632" width="840" height="52" rx="18" fill="#02080f" fill-opacity="0.82" stroke="#7de2ff" stroke-opacity="0.32"/>
|
|
571
|
+
<text x="640" y="665" text-anchor="middle" fill="#f8fff2" font-family="Arial, sans-serif" font-size="24" font-weight="700">${escapeXml(frame.caption)}</text>
|
|
572
|
+
</svg>`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function robotSvg(
|
|
576
|
+
id: string,
|
|
577
|
+
x: number,
|
|
578
|
+
y: number,
|
|
579
|
+
body: string,
|
|
580
|
+
mouth: string,
|
|
581
|
+
armAngle: number,
|
|
582
|
+
armEndX: number,
|
|
583
|
+
mouthOpen: number,
|
|
584
|
+
flip: boolean
|
|
585
|
+
): string {
|
|
586
|
+
const dir = flip ? -1 : 1;
|
|
587
|
+
const eyeX = dir * 20;
|
|
588
|
+
const mouthHeight = 5 + mouthOpen * 10;
|
|
589
|
+
return `<g id="${id}" transform="translate(${x.toFixed(1)} ${y.toFixed(1)})">
|
|
590
|
+
<ellipse cx="0" cy="116" rx="58" ry="14" fill="#020711" opacity="0.38"/>
|
|
591
|
+
<rect x="-42" y="-12" width="84" height="96" rx="36" fill="${body}" stroke="#071019" stroke-width="4"/>
|
|
592
|
+
<circle cx="0" cy="-58" r="46" fill="${body}" stroke="#071019" stroke-width="4"/>
|
|
593
|
+
<circle cx="${-eyeX}" cy="-66" r="6" fill="#06131c"/>
|
|
594
|
+
<circle cx="${eyeX}" cy="-66" r="6" fill="#06131c"/>
|
|
595
|
+
<rect x="-18" y="-43" width="36" height="${mouthHeight.toFixed(1)}" rx="5" fill="${mouth}"/>
|
|
596
|
+
<line x1="${dir * 36}" y1="12" x2="${(armEndX - x).toFixed(1)}" y2="${(44 + armAngle * 0.2).toFixed(1)}" stroke="${body}" stroke-width="18" stroke-linecap="round"/>
|
|
597
|
+
<line x1="${-dir * 34}" y1="18" x2="${(-dir * 92).toFixed(1)}" y2="${(42 - armAngle * 0.15).toFixed(1)}" stroke="${body}" stroke-width="18" stroke-linecap="round"/>
|
|
598
|
+
<line x1="-22" y1="78" x2="-38" y2="128" stroke="${body}" stroke-width="20" stroke-linecap="round"/>
|
|
599
|
+
<line x1="22" y1="78" x2="42" y2="126" stroke="${body}" stroke-width="20" stroke-linecap="round"/>
|
|
600
|
+
</g>`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function escapeXml(value: string): string {
|
|
604
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
605
|
+
}
|
|
@@ -4,26 +4,32 @@ import { AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID } from "./contract";
|
|
|
4
4
|
|
|
5
5
|
export const episodeContractId = AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID;
|
|
6
6
|
|
|
7
|
-
export type CartoonAssetKey = "miko" | "luma" | "glowBroom" | "glowStones" | "moonLilies";
|
|
7
|
+
export type CartoonAssetKey = "miko" | "luma" | "moonGarden" | "glowBroom" | "glowStones" | "moonLilies";
|
|
8
8
|
|
|
9
9
|
type TypedCartoonAssets = Partial<Record<CartoonAssetKey, AuraAssetRef<"model">>>;
|
|
10
10
|
|
|
11
11
|
const typedCartoonAssets = assets as TypedCartoonAssets;
|
|
12
12
|
|
|
13
13
|
export const requiredCartoonCharacterAssets = ["miko", "luma"] as const;
|
|
14
|
+
export const requiredCartoonSetAssets = ["moonGarden"] as const;
|
|
14
15
|
export const optionalCartoonPropAssets = ["glowBroom", "glowStones", "moonLilies"] as const;
|
|
15
16
|
export const publicCartoonAssetInstructions = [
|
|
16
|
-
"npx @aura3d/cli@latest assets
|
|
17
|
-
"npx @aura3d/cli@latest assets
|
|
18
|
-
"npx @aura3d/cli@latest assets
|
|
17
|
+
"npx @aura3d/cli@latest assets resolve \"stylized rigged cartoon child robot\" --name miko --profile cartoon-character",
|
|
18
|
+
"npx @aura3d/cli@latest assets resolve \"stylized rigged cartoon helper robot\" --name luma --profile cartoon-character",
|
|
19
|
+
"npx @aura3d/cli@latest assets resolve \"stylized moon garden set\" --name moonGarden --profile cartoon-set",
|
|
20
|
+
"npx @aura3d/cli@latest assets validate-cartoon --episode --require-license --no-placeholders"
|
|
19
21
|
] as const;
|
|
20
22
|
|
|
21
23
|
export const missingCartoonCharacterAssets = requiredCartoonCharacterAssets.filter((key) => !typedCartoonAssets[key]);
|
|
24
|
+
export const missingCartoonSetAssets = requiredCartoonSetAssets.filter((key) => !typedCartoonAssets[key]);
|
|
22
25
|
export const typedCartoonAssetSummary = {
|
|
23
26
|
requiredCharacterAssets: requiredCartoonCharacterAssets,
|
|
27
|
+
requiredSetAssets: requiredCartoonSetAssets,
|
|
24
28
|
optionalPropAssets: optionalCartoonPropAssets,
|
|
25
29
|
typedCharacterAssetCount: requiredCartoonCharacterAssets.length - missingCartoonCharacterAssets.length,
|
|
26
|
-
|
|
30
|
+
typedSetAssetCount: requiredCartoonSetAssets.length - missingCartoonSetAssets.length,
|
|
31
|
+
missingCharacterAssets: missingCartoonCharacterAssets,
|
|
32
|
+
missingSetAssets: missingCartoonSetAssets
|
|
27
33
|
} as const;
|
|
28
34
|
|
|
29
35
|
function typedAsset(key: CartoonAssetKey): AuraAssetRef<"model"> | undefined {
|
|
@@ -98,7 +104,7 @@ export const episode = compilePromptEpisodePlan({
|
|
|
98
104
|
runtime: {
|
|
99
105
|
duration: 60,
|
|
100
106
|
frameRate: 30,
|
|
101
|
-
resolution: { width:
|
|
107
|
+
resolution: { width: 1280, height: 720 },
|
|
102
108
|
aspectRatio: "16:9",
|
|
103
109
|
reducedMotion: true,
|
|
104
110
|
highContrast: true,
|