3eab1056c8
Adds audio to the scene engine without any third-party/geo-blocked sourcing: the beds + SFX are synthesized with ffmpeg, so they're license-free (CC0, self-authored) and need no acquisition — the same play as self-authoring Lottie. - public/audio/: music-ambient.mp3 (soft 3-tone pad, looped) + sfx-whoosh/pop/chime. - FlexStory: optional music/musicVolume/sfx props (optional so the existing render binding needs no change). Renders <Audio loop> for the bed + a whoosh at each scene start and a chime on the final scene, driven by precomputed scene starts. - check-assets: now also scans public/audio (+ lottie) with folder-prefixed keys; assets.json ledgers the 4 audio files (CC0 self-authored). Verified: tsc clean; a 6s FlexStory render produces an MP4 with a real audio stream (ffprobe: codec_type=audio). NOTE: these are placeholder/SFX-grade; a premium curated music library (by vibe) is a separate sourcing sweep, and the studio music picker → FlexStory `music` prop is a follow-up wiring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.8 KiB
JavaScript
49 lines
1.8 KiB
JavaScript
// License-firewall CI guard.
|
|
//
|
|
// Every vendored asset under public/illustrations/ (and public/lottie/) MUST have a
|
|
// row in public/illustrations/assets.json. No row -> the asset may not ship. Fails
|
|
// the build (exit 1) on any un-ledgered file. Run: npm run check:assets
|
|
import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
|
|
import { join, dirname, relative } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PUBLIC = join(__dirname, "..", "public");
|
|
const ILLUS = join(PUBLIC, "illustrations");
|
|
const ledgerPath = join(ILLUS, "assets.json");
|
|
|
|
if (!existsSync(ledgerPath)) {
|
|
console.error(`MISSING license ledger: ${ledgerPath}`);
|
|
process.exit(1);
|
|
}
|
|
const ledger = JSON.parse(readFileSync(ledgerPath, "utf8"));
|
|
|
|
function walk(dir) {
|
|
if (!existsSync(dir)) return [];
|
|
let out = [];
|
|
for (const e of readdirSync(dir)) {
|
|
const p = join(dir, e);
|
|
if (statSync(p).isDirectory()) out = out.concat(walk(p));
|
|
else if (/\.(svg|json|mp3|wav|ogg|m4a)$/i.test(e) && e !== "assets.json") out.push(p);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Ledger keys: illustrations are relative to illustrations/ (e.g. dicebear/x.svg);
|
|
// lottie + audio carry their folder prefix (lottie/x.json, audio/x.mp3).
|
|
const SCAN = [
|
|
{ root: ILLUS, prefix: "" },
|
|
{ root: join(PUBLIC, "lottie"), prefix: "lottie/" },
|
|
{ root: join(PUBLIC, "audio"), prefix: "audio/" },
|
|
];
|
|
const files = SCAN.flatMap(({ root, prefix }) =>
|
|
walk(root).map((p) => prefix + relative(root, p).split("\\").join("/"))
|
|
);
|
|
|
|
const missing = files.filter((f) => !ledger[f]);
|
|
if (missing.length) {
|
|
console.error(`Un-ledgered assets (add a row to assets.json):\n ${missing.join("\n ")}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`assets.json OK — ${files.length} vendored asset(s), all ledgered.`);
|