From d2d3925a18882159ff1ea9b679b459702af9381c Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 19 Nov 2025 21:44:48 -0800 Subject: [PATCH] fix(stt): add fallback for ffmpeg --- apps/sim/lib/audio/extractor.ts | 62 +++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/apps/sim/lib/audio/extractor.ts b/apps/sim/lib/audio/extractor.ts index e1e1ec7b29..5c328722c1 100644 --- a/apps/sim/lib/audio/extractor.ts +++ b/apps/sim/lib/audio/extractor.ts @@ -1,4 +1,5 @@ import { execSync } from 'node:child_process' +import fsSync from 'node:fs' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' @@ -10,26 +11,53 @@ import type { AudioMetadata, } from '@/lib/audio/types' -// Set ffmpeg binary path with fallback to system ffmpeg -try { +let ffmpegInitialized = false +let ffmpegPath: string | null = null + +/** + * Lazy initialization of FFmpeg - only runs when needed, not at module load + */ +function ensureFfmpeg(): void { + if (ffmpegInitialized) { + if (!ffmpegPath) { + throw new Error( + 'FFmpeg not found. Install: brew install ffmpeg (macOS) / apk add ffmpeg (Alpine) / apt-get install ffmpeg (Ubuntu)' + ) + } + return + } + + ffmpegInitialized = true + + // Try ffmpeg-static binary if (ffmpegStatic && typeof ffmpegStatic === 'string') { - ffmpeg.setFfmpegPath(ffmpegStatic) - } else { - // Try to find system ffmpeg try { - const systemFfmpeg = execSync('which ffmpeg', { encoding: 'utf-8' }).trim() - if (systemFfmpeg) { - ffmpeg.setFfmpegPath(systemFfmpeg) - console.log('[FFmpeg] Using system ffmpeg:', systemFfmpeg) - } + fsSync.accessSync(ffmpegStatic, fsSync.constants.X_OK) + ffmpegPath = ffmpegStatic + ffmpeg.setFfmpegPath(ffmpegPath) + console.log('[FFmpeg] Using ffmpeg-static:', ffmpegPath) + return } catch { - console.warn( - '[FFmpeg] ffmpeg-static not available and system ffmpeg not found. Please install ffmpeg: brew install ffmpeg (macOS) or apt-get install ffmpeg (Linux)' - ) + // Binary doesn't exist or not executable } } -} catch (error) { - console.warn('[FFmpeg] Failed to set ffmpeg path:', error) + + // Try system ffmpeg (cross-platform) + try { + const cmd = process.platform === 'win32' ? 'where ffmpeg' : 'which ffmpeg' + const result = execSync(cmd, { encoding: 'utf-8' }).trim() + // On Windows, 'where' returns multiple paths - take first + ffmpegPath = result.split('\n')[0] + ffmpeg.setFfmpegPath(ffmpegPath) + console.log('[FFmpeg] Using system ffmpeg:', ffmpegPath) + return + } catch { + // System ffmpeg not found + } + + // No FFmpeg found - set flag but don't throw yet + // Error will be thrown when user tries to use video extraction + console.warn('[FFmpeg] No FFmpeg binary found at module load time') } /** @@ -40,6 +68,8 @@ export async function extractAudioFromVideo( mimeType: string, options: AudioExtractionOptions = {} ): Promise { + // Initialize FFmpeg on first use + ensureFfmpeg() const isVideo = mimeType.startsWith('video/') const isAudio = mimeType.startsWith('audio/') @@ -152,6 +182,7 @@ async function convertAudioWithFFmpeg( * Get audio metadata using ffprobe */ export async function getAudioMetadata(buffer: Buffer, mimeType: string): Promise { + ensureFfmpeg() // Initialize FFmpeg/ffprobe const tempDir = os.tmpdir() const inputExt = getExtensionFromMimeType(mimeType) const inputFile = path.join(tempDir, `ffprobe-input-${Date.now()}.${inputExt}`) @@ -176,6 +207,7 @@ export async function getAudioMetadata(buffer: Buffer, mimeType: string): Promis * Get audio metadata from a file path using ffprobe */ async function getAudioMetadataFromFile(filePath: string): Promise { + ensureFfmpeg() // Initialize FFmpeg/ffprobe return new Promise((resolve, reject) => { ffmpeg.ffprobe(filePath, (err, metadata) => { if (err) {