Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 47 additions & 15 deletions apps/sim/lib/audio/extractor.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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')
}

/**
Expand All @@ -40,6 +68,8 @@ export async function extractAudioFromVideo(
mimeType: string,
options: AudioExtractionOptions = {}
): Promise<AudioExtractionResult> {
// Initialize FFmpeg on first use
ensureFfmpeg()
const isVideo = mimeType.startsWith('video/')
const isAudio = mimeType.startsWith('audio/')

Expand Down Expand Up @@ -152,6 +182,7 @@ async function convertAudioWithFFmpeg(
* Get audio metadata using ffprobe
*/
export async function getAudioMetadata(buffer: Buffer, mimeType: string): Promise<AudioMetadata> {
ensureFfmpeg() // Initialize FFmpeg/ffprobe
const tempDir = os.tmpdir()
const inputExt = getExtensionFromMimeType(mimeType)
const inputFile = path.join(tempDir, `ffprobe-input-${Date.now()}.${inputExt}`)
Expand All @@ -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<AudioMetadata> {
ensureFfmpeg() // Initialize FFmpeg/ffprobe
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, metadata) => {
if (err) {
Expand Down