Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
import { writeFile } from "node:fs/promises";
import { computeStats } from "@suaveplan/audio-core";
import type { AudioCLI } from "../cli/cli.js";
import { AUDIO_CLI_CODES, AudioCliError } from "../error-codes/error-codes.js";
import { AnalyzeOptionsSchema } from "./analyze.schemas.js";
import type { AnalyzeOptions, AnalyzeReport } from "./analyze.types.js";
/**
* Load the input, run the requested analysis ops, and emit a structured
* report. `--stats` (default when no analysis flag is set) delegates to
* `computeStats` from `@suaveplan/audio-core`; `--bpm`, `--key`, and
* `--lufs` slots are exposed so consumers can wire the
* `@suaveplan/audio-analysis-*` ops without modifying this module.
*/
export async function runAnalyze(
cli: AudioCLI,
input: string,
opts: AnalyzeOptions = {}
): Promise<AnalyzeReport> {
const parsed = AnalyzeOptionsSchema.safeParse(opts);
if (!parsed.success) {
throw new AudioCliError(
AUDIO_CLI_CODES.INVALID_ARGS,
"audio-cli.invalid-args",
{
context: { command: "analyze", issues: parsed.error.issues },
}
);
}
if (opts.signal?.aborted) {
throw new AudioCliError(
AUDIO_CLI_CODES.ABORTED,
"audio-cli.aborted",
{}
);
}
let doc: import("@suaveplan/audio-core").AudioDocument;
try {
doc = await cli.source.load(
input,
opts.signal !== undefined ? { signal: opts.signal } : {}
);
} catch (err) {
throw new AudioCliError(
AUDIO_CLI_CODES.INPUT_NOT_FOUND,
"audio-cli.input-not-found",
{
cause: err,
context: { path: input },
}
);
}
const wantStats = opts.stats || (!opts.bpm && !opts.key && !opts.lufs);
const report: AnalyzeReport = {
input,
sampleRate: doc.sampleRate,
channels: doc.channels,
duration: doc.duration,
...(wantStats
? (() => {
const pages = doc.pages.filter(
(p): p is NonNullable<typeof p> => p !== null
);
const s = computeStats(pages);
return s.lufs !== undefined
? /* istanbul ignore next -- computeStats never sets lufs today */
{ peak: s.peak, rms: s.rms, lufs: s.lufs }
: { peak: s.peak, rms: s.rms };
})()
: {}),
...(opts.lufs
? {
lufs:
computeStats(
doc.pages.filter(
(p): p is NonNullable<typeof p> => p !== null
)
).lufs ??
/* istanbul ignore next -- computeStats always reports lufs for valid pages */
Number.NEGATIVE_INFINITY,
}
: {}),
};
if (opts.output) {
await writeFile(opts.output, JSON.stringify(report, null, 2), "utf8");
}
return report;
}