Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586#!/usr/bin/env node// pop/bin/gen-ref-motion.mjs — reference-to-video via Seedance 2.0 on// fal.ai. Generate a FRESH clip steered by --prompt, pulling style /// motion / sound from reference media (not frame-locked restyling).//// Refer to the refs in the prompt by index: @Image1, @Video1, @Audio1.// Caps: ≤9 images (≤30MB ea) · ≤3 videos (combined 2–15s, <50MB total,// ~480–720p) · ≤3 audio (combined ≤15s, ≤15MB ea). Audio needs ≥1// image or video alongside it.//// Usage:// node pop/bin/gen-ref-motion.mjs \// --video ~/Downloads/ref-prep/5-video-720p.mp4 \// --audio ~/Downloads/ref-prep/5-audio.mp3 \// --prompt "..." \// [--image path.png] # repeatable: --image a --image b// [--duration auto] # 4..15 | auto// [--ratio auto] # auto | 21:9 | 16:9 | 4:3 | 1:1 | 3:4 | 9:16// [--resolution 720p] # 480p | 720p | 1080p// [--tier fast] # fast | standard// [--no-audio] # disable synthesized audio (default ON)// [--seed N] [--out path.mp4]//// FAL_KEY from env or vault devcontainer.env.
import { existsSync } from "node:fs";import { resolve, basename } from "node:path";import { generateReferenceShot, RATE_PER_SEC } from "../lib/fal.mjs";
// Parse flags; --image/--video/--audio repeat into arrays.const flags = {};const multi = { image: [], video: [], audio: [] };for (let i = 2; i < process.argv.length; i++) { const a = process.argv[i]; if (!a.startsWith("--")) continue; const k = a.slice(2); const next = process.argv[i + 1]; const val = next !== undefined && !next.startsWith("--") ? (i++, next) : true; if (k in multi && val !== true) multi[k].push(val); else flags[k] = val;}
const abs = (p) => resolve(process.cwd(), p.replace(/^~/, process.env.HOME));const PROMPT = flags.prompt;if (!PROMPT || PROMPT === true) { console.error("✗ --prompt required"); process.exit(1); }
const images = multi.image.map(abs);const videos = multi.video.map(abs);const audios = multi.audio.map(abs);for (const p of [...images, ...videos, ...audios]) { if (!existsSync(p)) { console.error(`✗ ref not found: ${p}`); process.exit(1); }}if (!videos.length && !images.length) { console.error("✗ need at least one --video or --image ref"); process.exit(1);}
const TIER = flags.tier || "fast";const DURATION = String(flags.duration ?? "auto");const RESOLUTION = flags.resolution || "720p";const RATIO = flags.ratio || "auto";const OUT = flags.out ? abs(flags.out) : abs(`~/Downloads/ref-prep/ref-motion-${TIER}-${DURATION}s.mp4`);
console.log(`▸ reference-to-video · ${TIER} ${RESOLUTION} ${DURATION}s ${RATIO}`);if (images.length) console.log(` images: ${images.map((p) => basename(p)).join(", ")}`);if (videos.length) console.log(` videos: ${videos.map((p) => basename(p)).join(", ")}`);if (audios.length) console.log(` audio: ${audios.map((p) => basename(p)).join(", ")}`);console.log(` prompt: ${PROMPT.slice(0, 140)}${PROMPT.length > 140 ? "…" : ""}`);if (DURATION !== "auto") { const est = Number(DURATION) * RATE_PER_SEC[TIER]; console.log(` ~$${est.toFixed(2)} @720p (video refs get a 0.6× multiplier)`);}if (flags["dry-run"]) { console.log(" (dry run — not submitting)"); process.exit(0); }
const res = await generateReferenceShot({ images, videos, audios, prompt: PROMPT, duration: DURATION, ratio: RATIO, resolution: RESOLUTION, tier: TIER, audio: flags["no-audio"] !== true, seed: flags.seed ? Number(flags.seed) : null, outPath: OUT, label: "ref",});
if (!res.ok) { console.error(`✗ ${res.error}`); process.exit(1); }console.log(`✓ ${OUT.replace(process.env.HOME, "~")} (${(res.bytes / 1e6).toFixed(1)} MB, seed ${res.seed}, ${res.seconds.toFixed(0)}s)`);