// Where the heavy, re-derivable data lives. // // The tooling and the small JSON state moved into the repo (this directory) so // they survive the job that made them. The 39 GB that feeds them -- wav48/, // media/, cand2/, asr/, intro/, wav/, the per-tune plan files and the render // scratch -- is re-derivable from the archive, just slowly. // // So there are two roots, and the distinction is worth keeping straight: // // DIR each script's own directory -- code and durable state // SONG_DATA the bulk data // // The default is `data/` INSIDE the deliverables tree (SONG_REPORTS, below), // ~/reports/quartering-uh-song/data. It used to be ~/.local/share/archilyzer/song // with the data itself left in a job's temp directory, and when that job was // cleaned up the whole corpus went with it -- the Mortal Kombat body, its // backgrounds and separated vocals included, so a finished song could no longer // be re-mixed. Bulk data lives beside the deliverables it makes, never in a // scratch or job directory. // // Point SONG_DIR at a copy to run the toolchain somewhere else; a symlink at the // default is still the way to keep the data on another disk. // // SONG_DATA is that path's REALPATH, so lib/paths.mjs's SONG_SCRATCH // (dirname(SONG_DATA)) keeps pointing at the real directory the data sits in. A // path that does not exist has no realpath; it is used as given, so a machine // with no song data still gets a SONG_DATA -- one every reader finds empty // (e2e/fixtures/song-capabilities.mjs says which specs that skips). import { realpathSync } from "node:fs"; import os from "node:os"; import path from "node:path"; const realOrAsGiven = (p) => { try { return realpathSync(p); } catch { return p; } }; /** * The um-song deliverables tree (thumbs/, videos/, the .plan.json files). * Defined HERE rather than in lib/paths.mjs, which re-exports it, because the * song scripts import only their siblings: the e2e fixture copies this * directory's .mjs files into its own code/ and runs them from there, where * ../lib does not exist. */ export const SONG_REPORTS = path.resolve( process.env.SONG_REPORTS_DIR ?? path.join(os.homedir(), "reports", "quartering-uh-song"), ); export const SONG_DATA = realOrAsGiven( path.resolve(process.env.SONG_DIR ?? path.join(SONG_REPORTS, "data")), ); /** * `p` relative to `root` when it lies inside it, else `p` unchanged. * * For the paths a song script RECORDS in a tracked JSON file * (thumb-manifest.json, thumb-accepted.json): an absolute path there is one * machine's home directory. The readers take either form -- lib/paths.mjs * resolveInRoots binds a relative path to the FIRST root, SONG_REPORTS. Both * sides are compared as given AND through realpath (of the deepest part that * exists, so a file not written yet still counts), so a path spelled via the * symlink is inside the realpath'd SONG_DATA. A relative `p` is * returned as it is. */ export function relTo(root, p) { if (typeof p !== "string" || !path.isAbsolute(p)) return p; const real = (x) => { const rest = []; for (let cur = path.resolve(x); ; cur = path.dirname(cur)) { try { return path.join(realpathSync(cur), ...rest); } catch { if (path.dirname(cur) === cur) return path.resolve(x); rest.unshift(path.basename(cur)); } } }; const roots = [...new Set([path.resolve(root), real(root)])]; const paths = [...new Set([path.resolve(p), real(p)])]; for (const r of roots) { for (const abs of paths) { if (abs.startsWith(r + path.sep)) return path.relative(r, abs); } } return p; }