Files

49 lines
2.0 KiB
JavaScript

// Run in Node.js 22+ or a trusted backend, not in a browser with a private key.
import { writeFile } from "node:fs/promises";
const baseUrl = process.env.FASTVIDEO_BASE_URL || "http://127.0.0.1:8000/v1";
const headers = {
Authorization: `Bearer ${process.env.FASTVIDEO_API_KEY || "local"}`,
"Content-Type": "application/json",
};
const body = {
model: process.env.FASTVIDEO_MODEL || "stable-audio",
prompt: process.env.FASTVIDEO_PROMPT || "Rain on a metal roof, distant thunder.",
seconds: Number(process.env.FASTVIDEO_SECONDS || 6),
};
if (process.env.FASTVIDEO_VIDEO_REFERENCE) {
body.video_reference = { video_url: process.env.FASTVIDEO_VIDEO_REFERENCE };
}
let response = await fetch(`${baseUrl}/videos`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!response.ok) throw new Error(`Submit failed (${response.status}): ${await response.text()}`);
let job = await response.json();
console.log(`Submitted ${job.id}`);
const deadline = performance.now() + 1_800_000;
while (job.status === "queued" || job.status === "in_progress") {
const remaining = deadline - performance.now();
if (remaining <= 0) throw new Error(`Polling timed out; job ${job.id} may still be running`);
await new Promise((resolve) => setTimeout(resolve, Math.min(2000, remaining)));
response = await fetch(`${baseUrl}/videos/${job.id}`, { headers });
if (!response.ok) throw new Error(`Poll failed (${response.status}): ${await response.text()}`);
job = await response.json();
}
if (job.status !== "completed") {
throw new Error(`Audio job ${job.id} failed: ${job.error?.message || job.status}`);
}
if (job.media_type !== "audio/wav") {
throw new Error(`Expected audio/wav, got ${job.media_type}`);
}
response = await fetch(`${baseUrl}/videos/${job.id}/content`, { headers });
if (!response.ok) throw new Error(`Download failed (${response.status}): ${await response.text()}`);
const output = `${job.id}.wav`;
await writeFile(output, Buffer.from(await response.arrayBuffer()));
console.log(`Saved ${output}`);