mulmocast 2.4.0 → 2.4.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/assets/html/tailwind_animated.html +5 -5
- package/lib/actions/image_agents.d.ts +1 -0
- package/lib/actions/image_agents.js +4 -1
- package/lib/actions/images.d.ts +3 -0
- package/lib/actions/images.js +2 -1
- package/lib/types/type.d.ts +1 -0
- package/lib/utils/ffmpeg_utils.d.ts +1 -1
- package/lib/utils/ffmpeg_utils.js +6 -2
- package/lib/utils/image_plugins/html_tailwind.js +2 -2
- package/package.json +1 -1
- package/scripts/test/test_html_animation.json +77 -0
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
|
|
56
56
|
// === MulmoAnimation Helper Class ===
|
|
57
57
|
|
|
58
|
-
const TRANSFORM_PROPS = { translateX: 'px', translateY: 'px', scale: '', rotate: 'deg' };
|
|
58
|
+
const TRANSFORM_PROPS = { translateX: 'px', translateY: 'px', scale: '', rotate: 'deg', rotateX: 'deg', rotateY: 'deg', rotateZ: 'deg' };
|
|
59
59
|
const SVG_PROPS = ['r', 'cx', 'cy', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'rx', 'ry',
|
|
60
60
|
'width', 'height', 'stroke-width', 'stroke-dashoffset', 'stroke-dasharray', 'opacity'];
|
|
61
61
|
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
if (entry.kind === 'animate') {
|
|
178
178
|
const startFrame = (opts.start || 0) * fps;
|
|
179
|
-
const endFrame = (opts.end || 0) * fps;
|
|
179
|
+
const endFrame = (opts.end === 'auto' ? window.__MULMO.totalFrames / fps : (opts.end || 0)) * fps;
|
|
180
180
|
const progress = Math.max(0, Math.min(1, endFrame === startFrame ? 1 : (frame - startFrame) / (endFrame - startFrame)));
|
|
181
181
|
const el = document.querySelector(entry.selector);
|
|
182
182
|
this._applyProps(el, entry.props, progress, easingFn);
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
|
|
197
197
|
} else if (entry.kind === 'typewriter') {
|
|
198
198
|
const twStart = (opts.start || 0) * fps;
|
|
199
|
-
const twEnd = (opts.end || 0) * fps;
|
|
199
|
+
const twEnd = (opts.end === 'auto' ? window.__MULMO.totalFrames / fps : (opts.end || 0)) * fps;
|
|
200
200
|
const twProgress = Math.max(0, Math.min(1, twEnd === twStart ? 1 : (frame - twStart) / (twEnd - twStart)));
|
|
201
201
|
const charCount = Math.floor(twProgress * entry.text.length);
|
|
202
202
|
const twEl = document.querySelector(entry.selector);
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
|
|
205
205
|
} else if (entry.kind === 'counter') {
|
|
206
206
|
const cStart = (opts.start || 0) * fps;
|
|
207
|
-
const cEnd = (opts.end || 0) * fps;
|
|
207
|
+
const cEnd = (opts.end === 'auto' ? window.__MULMO.totalFrames / fps : (opts.end || 0)) * fps;
|
|
208
208
|
const cProgress = Math.max(0, Math.min(1, cEnd === cStart ? 1 : (frame - cStart) / (cEnd - cStart)));
|
|
209
209
|
const cVal = entry.range[0] + easingFn(cProgress) * (entry.range[1] - entry.range[0]);
|
|
210
210
|
const decimals = opts.decimals || 0;
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
|
|
215
215
|
} else if (entry.kind === 'codeReveal') {
|
|
216
216
|
const crStart = (opts.start || 0) * fps;
|
|
217
|
-
const crEnd = (opts.end || 0) * fps;
|
|
217
|
+
const crEnd = (opts.end === 'auto' ? window.__MULMO.totalFrames / fps : (opts.end || 0)) * fps;
|
|
218
218
|
const crProgress = Math.max(0, Math.min(1, crEnd === crStart ? 1 : (frame - crStart) / (crEnd - crStart)));
|
|
219
219
|
const lineCount = Math.floor(crProgress * entry.lines.length);
|
|
220
220
|
const crEl = document.querySelector(entry.selector);
|
|
@@ -41,6 +41,7 @@ type ImageHtmlPreprocessAgentResponse = {
|
|
|
41
41
|
};
|
|
42
42
|
type ImageOnlyMoviePreprocessAgentResponse = ImagePreprocessAgentResponseBase & {
|
|
43
43
|
imageFromMovie: boolean;
|
|
44
|
+
useLastFrame?: boolean;
|
|
44
45
|
};
|
|
45
46
|
type ImagePluginPreprocessAgentResponse = ImagePreprocessAgentResponseBase & {
|
|
46
47
|
referenceImageForMovie: string;
|
|
@@ -85,6 +85,7 @@ export const imagePreprocessAgent = async (namedInputs) => {
|
|
|
85
85
|
imagePath, // for thumbnail extraction
|
|
86
86
|
movieFile: animatedVideoPath, // .mp4 path for the pipeline
|
|
87
87
|
imageFromMovie: true, // triggers extractImageFromMovie
|
|
88
|
+
useLastFrame: true, // extract last frame for PDF/static (animation complete state)
|
|
88
89
|
referenceImageForMovie: pluginPath,
|
|
89
90
|
markdown,
|
|
90
91
|
html,
|
|
@@ -122,7 +123,9 @@ export const imagePluginAgent = async (namedInputs) => {
|
|
|
122
123
|
const effectiveImagePath = isAnimatedHtml ? getBeatAnimatedVideoPath(context, index) : imagePath;
|
|
123
124
|
try {
|
|
124
125
|
MulmoStudioContextMethods.setBeatSessionState(context, "image", index, beat.id, true);
|
|
125
|
-
const
|
|
126
|
+
const studioBeat = context.studio.beats[index];
|
|
127
|
+
const beatDuration = beat.duration ?? studioBeat?.duration;
|
|
128
|
+
const processorParams = { beat, context, imagePath: effectiveImagePath, imageRefs, beatDuration, ...htmlStyle(context, beat) };
|
|
126
129
|
await plugin.process(processorParams);
|
|
127
130
|
MulmoStudioContextMethods.setBeatSessionState(context, "image", index, beat.id, false);
|
|
128
131
|
}
|
package/lib/actions/images.d.ts
CHANGED
|
@@ -118,6 +118,7 @@ export declare const beat_graph_data: {
|
|
|
118
118
|
imagePath?: string;
|
|
119
119
|
} & {
|
|
120
120
|
imageFromMovie: boolean;
|
|
121
|
+
useLastFrame?: boolean;
|
|
121
122
|
}) | ({
|
|
122
123
|
imageParams?: MulmoImageParams;
|
|
123
124
|
movieFile?: string;
|
|
@@ -286,11 +287,13 @@ export declare const beat_graph_data: {
|
|
|
286
287
|
agent: (namedInputs: {
|
|
287
288
|
movieFile: string;
|
|
288
289
|
imageFile: string;
|
|
290
|
+
useLastFrame: boolean;
|
|
289
291
|
}) => Promise<object>;
|
|
290
292
|
inputs: {
|
|
291
293
|
onComplete: string[];
|
|
292
294
|
imageFile: string;
|
|
293
295
|
movieFile: string;
|
|
296
|
+
useLastFrame: string;
|
|
294
297
|
};
|
|
295
298
|
defaultValue: {};
|
|
296
299
|
};
|
package/lib/actions/images.js
CHANGED
|
@@ -187,12 +187,13 @@ export const beat_graph_data = {
|
|
|
187
187
|
imageFromMovie: {
|
|
188
188
|
if: ":preprocessor.imageFromMovie",
|
|
189
189
|
agent: async (namedInputs) => {
|
|
190
|
-
return await extractImageFromMovie(namedInputs.movieFile, namedInputs.imageFile);
|
|
190
|
+
return await extractImageFromMovie(namedInputs.movieFile, namedInputs.imageFile, namedInputs.useLastFrame);
|
|
191
191
|
},
|
|
192
192
|
inputs: {
|
|
193
193
|
onComplete: [":movieGenerator", ":imagePlugin"], // :imagePlugin for animated html_tailwind video generation
|
|
194
194
|
imageFile: ":preprocessor.imagePath",
|
|
195
195
|
movieFile: ":preprocessor.movieFile",
|
|
196
|
+
useLastFrame: ":preprocessor.useLastFrame",
|
|
196
197
|
},
|
|
197
198
|
defaultValue: {},
|
|
198
199
|
},
|
package/lib/types/type.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export type ImageProcessorParams = {
|
|
|
84
84
|
textSlideStyle: string;
|
|
85
85
|
canvasSize: MulmoCanvasDimension;
|
|
86
86
|
imageRefs?: Record<string, string>;
|
|
87
|
+
beatDuration?: number;
|
|
87
88
|
};
|
|
88
89
|
export type PDFMode = (typeof pdf_modes)[number];
|
|
89
90
|
export type PDFSize = (typeof pdf_sizes)[number];
|
|
@@ -25,7 +25,7 @@ export declare const ffmpegGetMediaDuration: (filePath: string) => Promise<{
|
|
|
25
25
|
duration: number;
|
|
26
26
|
hasAudio: boolean;
|
|
27
27
|
}>;
|
|
28
|
-
export declare const extractImageFromMovie: (movieFile: string, imagePath: string) => Promise<object>;
|
|
28
|
+
export declare const extractImageFromMovie: (movieFile: string, imagePath: string, useLastFrame?: boolean) => Promise<object>;
|
|
29
29
|
export declare const trimMusic: (inputFile: string, startTime: number, duration: number) => Promise<Buffer>;
|
|
30
30
|
export declare const createSilentAudio: (filePath: string, durationSec: number) => Promise<void>;
|
|
31
31
|
export declare const pcmToMp3: (rawPcm: Buffer, sampleRate?: number) => Promise<Buffer>;
|
|
@@ -117,9 +117,13 @@ export const ffmpegGetMediaDuration = (filePath) => {
|
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
};
|
|
120
|
-
export const extractImageFromMovie = (movieFile, imagePath) => {
|
|
120
|
+
export const extractImageFromMovie = (movieFile, imagePath, useLastFrame = false) => {
|
|
121
121
|
return new Promise((resolve, reject) => {
|
|
122
|
-
ffmpeg(movieFile)
|
|
122
|
+
const command = ffmpeg(movieFile);
|
|
123
|
+
if (useLastFrame) {
|
|
124
|
+
command.inputOptions(["-sseof", "-0.1"]);
|
|
125
|
+
}
|
|
126
|
+
command
|
|
123
127
|
.outputOptions(["-frames:v 1"])
|
|
124
128
|
.output(imagePath)
|
|
125
129
|
.on("end", () => resolve({}))
|
|
@@ -34,9 +34,9 @@ const processHtmlTailwindAnimated = async (params) => {
|
|
|
34
34
|
const animConfig = getAnimationConfig(params);
|
|
35
35
|
if (!animConfig)
|
|
36
36
|
return;
|
|
37
|
-
const duration = beat.duration;
|
|
37
|
+
const duration = params.beatDuration ?? beat.duration;
|
|
38
38
|
if (duration === undefined) {
|
|
39
|
-
throw new Error("html_tailwind animation requires
|
|
39
|
+
throw new Error("html_tailwind animation requires beat.duration or audio-derived duration. Set duration in the beat or ensure audio is generated first.");
|
|
40
40
|
}
|
|
41
41
|
const fps = animConfig.fps;
|
|
42
42
|
const totalFrames = Math.floor(duration * fps);
|
package/package.json
CHANGED
|
@@ -546,6 +546,83 @@
|
|
|
546
546
|
"animation": { "fps": 24 }
|
|
547
547
|
}
|
|
548
548
|
},
|
|
549
|
+
{
|
|
550
|
+
"id": "demo_3d_card_flip",
|
|
551
|
+
"speaker": "Presenter",
|
|
552
|
+
"duration": 3,
|
|
553
|
+
"image": {
|
|
554
|
+
"type": "html_tailwind",
|
|
555
|
+
"html": [
|
|
556
|
+
"<div class='h-full flex items-center justify-center bg-gradient-to-br from-slate-900 to-indigo-950'>",
|
|
557
|
+
" <div style='perspective:1000px'>",
|
|
558
|
+
" <div id='card' class='relative' style='width:340px;height:200px;transform-style:preserve-3d'>",
|
|
559
|
+
" <div class='absolute inset-0 rounded-2xl flex flex-col items-center justify-center' style='backface-visibility:hidden;background:linear-gradient(135deg,#3b82f6,#06b6d4);box-shadow:0 20px 60px rgba(6,182,212,0.3)'>",
|
|
560
|
+
" <p class='text-white text-3xl font-bold tracking-wide'>MulmoCast</p>",
|
|
561
|
+
" <p class='text-blue-200 text-sm mt-2 tracking-wider'>FRONT SIDE</p>",
|
|
562
|
+
" </div>",
|
|
563
|
+
" <div class='absolute inset-0 rounded-2xl flex flex-col items-center justify-center' style='backface-visibility:hidden;transform:rotateY(180deg);background:linear-gradient(135deg,#8b5cf6,#ec4899);box-shadow:0 20px 60px rgba(139,92,246,0.3)'>",
|
|
564
|
+
" <p class='text-white text-3xl font-bold tracking-wide'>AI-Native</p>",
|
|
565
|
+
" <p class='text-purple-200 text-sm mt-2 tracking-wider'>BACK SIDE</p>",
|
|
566
|
+
" </div>",
|
|
567
|
+
" </div>",
|
|
568
|
+
" </div>",
|
|
569
|
+
"</div>"
|
|
570
|
+
],
|
|
571
|
+
"script": [
|
|
572
|
+
"const animation = new MulmoAnimation();",
|
|
573
|
+
"animation.animate('#card', { rotateY: [0, 180] }, { start: 0.5, end: 2.5, easing: 'easeInOut' });"
|
|
574
|
+
],
|
|
575
|
+
"animation": true
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"id": "demo_3d_title_reveal",
|
|
580
|
+
"speaker": "Presenter",
|
|
581
|
+
"duration": 3,
|
|
582
|
+
"image": {
|
|
583
|
+
"type": "html_tailwind",
|
|
584
|
+
"html": [
|
|
585
|
+
"<div class='h-full flex flex-col items-center justify-center bg-black' style='perspective:800px'>",
|
|
586
|
+
" <h1 id='title' class='text-7xl font-bold tracking-wider' style='opacity:0;font-family:Impact,sans-serif;color:white;text-shadow:0 0 40px rgba(6,182,212,0.5)'>CINEMATIC</h1>",
|
|
587
|
+
" <div id='line' class='h-0.5 mt-6 rounded' style='width:0;background:linear-gradient(90deg,transparent,#06b6d4,transparent)'></div>",
|
|
588
|
+
" <p id='sub' class='text-lg mt-6 tracking-[0.4em]' style='opacity:0;color:#64748b;font-family:monospace'>3D PERSPECTIVE REVEAL</p>",
|
|
589
|
+
"</div>"
|
|
590
|
+
],
|
|
591
|
+
"script": [
|
|
592
|
+
"const animation = new MulmoAnimation();",
|
|
593
|
+
"animation.animate('#title', { opacity: [0, 1], rotateX: [90, 0] }, { start: 0.2, end: 1.2, easing: 'easeOut' });",
|
|
594
|
+
"animation.animate('#line', { width: [0, 400, 'px'] }, { start: 1.0, end: 1.8, easing: 'easeOut' });",
|
|
595
|
+
"animation.animate('#sub', { opacity: [0, 1] }, { start: 1.5, end: 2.2, easing: 'easeOut' });"
|
|
596
|
+
],
|
|
597
|
+
"animation": true
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
"id": "demo_split_reveal",
|
|
602
|
+
"speaker": "Presenter",
|
|
603
|
+
"duration": 3,
|
|
604
|
+
"image": {
|
|
605
|
+
"type": "html_tailwind",
|
|
606
|
+
"html": [
|
|
607
|
+
"<div class='h-full flex bg-black overflow-hidden'>",
|
|
608
|
+
" <div id='left' class='flex-1 flex items-center justify-center' style='background:linear-gradient(135deg,#1e3a5f,#0f172a);opacity:0'>",
|
|
609
|
+
" <p class='text-6xl font-bold text-white' style='font-family:Georgia,serif'>Create</p>",
|
|
610
|
+
" </div>",
|
|
611
|
+
" <div id='divider' class='w-1' style='background:linear-gradient(to bottom,transparent,#06b6d4,transparent);opacity:0'></div>",
|
|
612
|
+
" <div id='right' class='flex-1 flex items-center justify-center' style='background:linear-gradient(225deg,#4c1d95,#0f172a);opacity:0'>",
|
|
613
|
+
" <p class='text-6xl font-bold text-white' style='font-family:Georgia,serif'>Inspire</p>",
|
|
614
|
+
" </div>",
|
|
615
|
+
"</div>"
|
|
616
|
+
],
|
|
617
|
+
"script": [
|
|
618
|
+
"const animation = new MulmoAnimation();",
|
|
619
|
+
"animation.animate('#left', { translateX: [-640, 0], opacity: [0, 1] }, { start: 0, end: 1.0, easing: 'easeOut' });",
|
|
620
|
+
"animation.animate('#right', { translateX: [640, 0], opacity: [0, 1] }, { start: 0.3, end: 1.3, easing: 'easeOut' });",
|
|
621
|
+
"animation.animate('#divider', { opacity: [0, 1] }, { start: 1.2, end: 1.8 });"
|
|
622
|
+
],
|
|
623
|
+
"animation": true
|
|
624
|
+
}
|
|
625
|
+
},
|
|
549
626
|
{
|
|
550
627
|
"speaker": "Presenter",
|
|
551
628
|
"duration": 2,
|