(audioPath: string)
| 34 | } |
| 35 | |
| 36 | export function decodeAudioPeaks(audioPath: string): Promise<number[]> { |
| 37 | return new Promise((resolvePromise, reject) => { |
| 38 | const proc = spawn( |
| 39 | ffmpegBinary(), |
| 40 | [ |
| 41 | "-i", |
| 42 | audioPath, |
| 43 | "-af", |
| 44 | "atrim=start_sample=1152", |
| 45 | "-f", |
| 46 | "f32le", |
| 47 | "-ac", |
| 48 | "1", |
| 49 | "-ar", |
| 50 | String(SAMPLE_RATE), |
| 51 | "-vn", |
| 52 | "pipe:1", |
| 53 | ], |
| 54 | { stdio: ["ignore", "pipe", "ignore"] }, |
| 55 | ); |
| 56 | |
| 57 | const chunks: Buffer[] = []; |
| 58 | proc.stdout?.on("data", (chunk: Buffer) => chunks.push(chunk)); |
| 59 | proc.on("close", (code) => { |
| 60 | if (code !== 0 && chunks.length === 0) { |
| 61 | reject(new Error(`ffmpeg exited with code ${code}`)); |
| 62 | return; |
| 63 | } |
| 64 | const buf = Buffer.concat(chunks); |
| 65 | const numSamples = Math.floor(buf.length / 4); |
| 66 | if (numSamples === 0) { |
| 67 | reject(new Error("ffmpeg produced no audio samples")); |
| 68 | return; |
| 69 | } |
| 70 | const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4); |
| 71 | resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT)); |
| 72 | }); |
| 73 | proc.on("error", reject); |
| 74 | }); |
| 75 | } |
| 76 | |
| 77 | export async function generateWaveformCache(projectDir: string, assetPath: string): Promise<void> { |
| 78 | const audioPath = join(projectDir, assetPath); |
no test coverage detected