11import { execSync } from 'node:child_process'
2+ import fsSync from 'node:fs'
23import fs from 'node:fs/promises'
34import os from 'node:os'
45import 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 */
154184export 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 */
178209async 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