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 { 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 => 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 => 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; }