CognxSafeTrack
chore: execute Sprint 38 technical debt resolution (Type Safety, Zod validation, Vitest, Mock LLM extracted)
d9879cf | import { exec } from 'child_process'; | |
| import { promisify } from 'util'; | |
| import { writeFile, readFile, unlink } from 'fs/promises'; | |
| import { join } from 'path'; | |
| import { randomBytes } from 'crypto'; | |
| const execAsync = promisify(exec); | |
| /** | |
| * Converts an audio buffer to MP3 using local FFmpeg if the file is OGG/OPUS. | |
| * This ensures compatibility with OpenAI Whisper, which frequently rejects native WhatsApp OPUS files. | |
| * Returns the original buffer if ffmpeg fails or isn't installed to attempt best-effort transcription. | |
| */ | |
| export async function convertToMp3IfNeeded(inputBuffer: Buffer, filename: string): Promise<{ buffer: Buffer; format: string }> { | |
| // We only strictly convert OGG or OPUS files (which are typical for WhatsApp). | |
| // If it's already an MP4/AAC or M4A, Whisper usually accepts it fine. | |
| if (!filename.toLowerCase().endsWith('.ogg') && !filename.toLowerCase().endsWith('.opus') && !filename.toLowerCase().endsWith('.oga')) { | |
| return { buffer: inputBuffer, format: filename.split('.').pop()! }; | |
| } | |
| const tempId = randomBytes(8).toString('hex'); | |
| const inputPath = join('/tmp', `in_${tempId}_${filename}`); | |
| const outputPath = join('/tmp', `out_${tempId}.mp3`); | |
| try { | |
| console.log(`[FFMPEG] Starting conversion for ${filename}...`); | |
| // Write the inbound buffer to a temp file | |
| await writeFile(inputPath, inputBuffer); | |
| // Run FFmpeg to convert it to a standard 128k MP3 | |
| // -y overwrites without prompting | |
| // -i specifies input file | |
| // -vn disables video just in case | |
| // -ar sets audio sample rate | |
| // -ac sets channels to mono (perfect for voice) | |
| // -b:a sets audio bitrate to 64k (sufficient for voice, saves bandwidth) | |
| await execAsync(`ffmpeg -y -i "${inputPath}" -vn -ar 44100 -ac 1 -b:a 64k "${outputPath}"`); | |
| // Read the converted file back into a buffer | |
| const mp3Buffer = await readFile(outputPath); | |
| console.log(`[FFMPEG] ✅ Successfully converted ${filename} to MP3.`); | |
| return { buffer: mp3Buffer, format: 'mp3' }; | |
| } catch (err: unknown) { | |
| console.error(`[FFMPEG] ⚠️ Conversion failed for ${filename}. Proceeding with original buffer. Error: ${(err instanceof Error ? (err instanceof Error ? err.message : String(err)) : String(err))}`); | |
| // If FFMPEG isn't installed or fails, we return the original buffer | |
| return { buffer: inputBuffer, format: filename.split('.').pop()! }; | |
| } finally { | |
| // Cleanup temp files asynchronously | |
| unlink(inputPath).catch(() => { }); | |
| unlink(outputPath).catch(() => { }); | |
| } | |
| } | |