Dee Ferdinand
feat: complete rewrite — correct HyperFrames compositions with GSAP + class=clip + window.__timelines
a2e4ffd
Raw
History Blame Contribute Delete
1.79 kB
import fs from 'fs';
import path from 'path';
function pick(media, preferVideo, idx) {
const v = media.filter(m => m.mime?.startsWith('video/'));
const i = media.filter(m => m.mime?.startsWith('image/'));
const pool = preferVideo ? (v.length ? v : i) : (i.length ? i : v);
return pool[idx % Math.max(pool.length, 1)] || media[idx % Math.max(media.length, 1)] || null;
}
export async function buildComposition(compDir, projectDir, musicPath, config, workflow, onProgress) {
const files = config._files || [];
const { trainerName = 'Dee Ferdinand', tagline = 'AI Corporate Trainer',
website = 'deeferdinand.com', clientName = '', format = '9:16', duration: totalDur = 30 } = config;
const assetsDir = path.join(compDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
// Copy uploaded files into assets/
const linkedMedia = [];
for (const f of files) {
const dest = path.join(assetsDir, path.basename(f.path));
if (!fs.existsSync(dest)) fs.copyFileSync(f.path, dest);
linkedMedia.push({ ...f, rel: `./assets/${path.basename(f.path)}` });
}
// Copy music
const musicDest = path.join(assetsDir, 'music.mp3');
if (!fs.existsSync(musicDest)) fs.copyFileSync(musicPath, musicDest);
onProgress?.(0.2);
const [w, h] = format === '9:16' ? [1080, 1920] : format === '1:1' ? [1080, 1080] : [1920, 1080];
const compId = `dee-${config.workflow}-${Date.now()}`;
// Write meta.json (HyperFrames reads this)
fs.writeFileSync(path.join(compDir, 'meta.json'), JSON.stringify(
{ name: compId, id: compId, duration: totalDur, width: w, height: h, fps: 30 }, null, 2
));
const html = workflow.buildHTML(linkedMedia, config, compId, w, h, totalDur);
fs.writeFileSync(path.join(compDir, 'index.html'), html);
onProgress?.(1.0);
}