Skip to content

Commit 4d5c574

Browse files
authored
fix(stt): add fallback for ffmpeg (#2073)
1 parent e7d4afa commit 4d5c574

File tree

1 file changed

+47
-15
lines changed

1 file changed

+47
-15
lines changed

apps/sim/lib/audio/extractor.ts

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execSync } from 'node:child_process'
2+
import fsSync from 'node:fs'
23
import fs from 'node:fs/promises'
34
import os from 'node:os'
45
import path from 'node:path'
@@ -10,26 +11,53 @@ import type {
1011
AudioMetadata,
1112
} from '@/lib/audio/types'
1213

13-
// Set ffmpeg binary path with fallback to system ffmpeg
14-
try {
14+
let ffmpegInitialized = false
15+
let ffmpegPath: string | null = null
16+
17+
/**
18+
* Lazy initialization of FFmpeg - only runs when needed, not at module load
19+
*/
20+
function ensureFfmpeg(): void {
21+
if (ffmpegInitialized) {
22+
if (!ffmpegPath) {
23+
throw new Error(
24+
'FFmpeg not found. Install: brew install ffmpeg (macOS) / apk add ffmpeg (Alpine) / apt-get install ffmpeg (Ubuntu)'
25+
)
26+
}
27+
return
28+
}
29+
30+
ffmpegInitialized = true
31+
32+
// Try ffmpeg-static binary
1533
if (ffmpegStatic && typeof ffmpegStatic === 'string') {
16-
ffmpeg.setFfmpegPath(ffmpegStatic)
17-
} else {
18-
// Try to find system ffmpeg
1934
try {
20-
const systemFfmpeg = execSync('which ffmpeg', { encoding: 'utf-8' }).trim()
21-
if (systemFfmpeg) {
22-
ffmpeg.setFfmpegPath(systemFfmpeg)
23-
console.log('[FFmpeg] Using system ffmpeg:', systemFfmpeg)
24-
}
35+
fsSync.accessSync(ffmpegStatic, fsSync.constants.X_OK)
36+
ffmpegPath = ffmpegStatic
37+
ffmpeg.setFfmpegPath(ffmpegPath)
38+
console.log('[FFmpeg] Using ffmpeg-static:', ffmpegPath)
39+
return
2540
} catch {
26-
console.warn(
27-
'[FFmpeg] ffmpeg-static not available and system ffmpeg not found. Please install ffmpeg: brew install ffmpeg (macOS) or apt-get install ffmpeg (Linux)'
28-
)
41+
// Binary doesn't exist or not executable
2942
}
3043
}
31-
} catch (error) {
32-
console.warn('[FFmpeg] Failed to set ffmpeg path:', error)
44+
45+
// Try system ffmpeg (cross-platform)
46+
try {
47+
const cmd = process.platform === 'win32' ? 'where ffmpeg' : 'which ffmpeg'
48+
const result = execSync(cmd, { encoding: 'utf-8' }).trim()
49+
// On Windows, 'where' returns multiple paths - take first
50+
ffmpegPath = result.split('\n')[0]
51+
ffmpeg.setFfmpegPath(ffmpegPath)
52+
console.log('[FFmpeg] Using system ffmpeg:', ffmpegPath)
53+
return
54+
} catch {
55+
// System ffmpeg not found
56+
}
57+
58+
// No FFmpeg found - set flag but don't throw yet
59+
// Error will be thrown when user tries to use video extraction
60+
console.warn('[FFmpeg] No FFmpeg binary found at module load time')
3361
}
3462

3563
/**
@@ -40,6 +68,8 @@ export async function extractAudioFromVideo(
4068
mimeType: string,
4169
options: AudioExtractionOptions = {}
4270
): Promise<AudioExtractionResult> {
71+
// Initialize FFmpeg on first use
72+
ensureFfmpeg()
4373
const isVideo = mimeType.startsWith('video/')
4474
const isAudio = mimeType.startsWith('audio/')
4575

@@ -152,6 +182,7 @@ async function convertAudioWithFFmpeg(
152182
* Get audio metadata using ffprobe
153183
*/
154184
export async function getAudioMetadata(buffer: Buffer, mimeType: string): Promise<AudioMetadata> {
185+
ensureFfmpeg() // Initialize FFmpeg/ffprobe
155186
const tempDir = os.tmpdir()
156187
const inputExt = getExtensionFromMimeType(mimeType)
157188
const inputFile = path.join(tempDir, `ffprobe-input-${Date.now()}.${inputExt}`)
@@ -176,6 +207,7 @@ export async function getAudioMetadata(buffer: Buffer, mimeType: string): Promis
176207
* Get audio metadata from a file path using ffprobe
177208
*/
178209
async function getAudioMetadataFromFile(filePath: string): Promise<AudioMetadata> {
210+
ensureFfmpeg() // Initialize FFmpeg/ffprobe
179211
return new Promise((resolve, reject) => {
180212
ffmpeg.ffprobe(filePath, (err, metadata) => {
181213
if (err) {

0 commit comments

Comments
 (0)