@@ -49,7 +49,11 @@ function isExecutablePathCandidate(candidate: string): boolean {
4949
5050function scanPath ( name : FfBinaryName ) : string | undefined {
5151 const pathValue = process . env . PATH ;
52- if ( ! pathValue ) return undefined ;
52+ const searchDirs = [
53+ ...( process . platform === "win32" ? [ process . cwd ( ) ] : [ ] ) ,
54+ ...( pathValue ? pathValue . split ( delimiter ) : [ ] ) ,
55+ ] . filter ( Boolean ) ;
56+ if ( searchDirs . length === 0 ) return undefined ;
5357
5458 const extensions =
5559 process . platform === "win32"
@@ -65,8 +69,7 @@ function scanPath(name: FfBinaryName): string | undefined {
6569 ]
6670 : [ "" ] ;
6771 const candidates : string [ ] = [ ] ;
68- for ( const dir of pathValue . split ( delimiter ) ) {
69- if ( ! dir ) continue ;
72+ for ( const dir of new Set ( searchDirs ) ) {
7073 for ( const ext of extensions ) {
7174 const candidate = join ( dir , `${ name } ${ ext } ` ) ;
7275 if ( isExecutablePathCandidate ( candidate ) ) candidates . push ( candidate ) ;
@@ -101,16 +104,24 @@ function findInProjectLocalBin(name: FfBinaryName): string | undefined {
101104function lookupOnSystem ( name : FfBinaryName ) : string | undefined {
102105 if ( pathLookupCache . has ( name ) ) return pathLookupCache . get ( name ) ;
103106 let found : string | undefined ;
104- try {
105- const command = process . platform === "win32" ? "where" : "which" ;
106- const output = execFileSync ( command , [ name ] , {
107- encoding : "utf-8" ,
108- stdio : [ "pipe" , "pipe" , "pipe" ] ,
109- timeout : 5000 ,
110- } ) ;
111- found = chooseBestPathCandidate ( name , output . split ( / \r ? \n / ) ) ;
112- } catch {
107+ if ( process . platform === "win32" ) {
108+ // `where.exe` writes bytes in the active console code page, while Node
109+ // decodes its stdout using a caller-selected encoding. Enumerating the
110+ // same current-directory + PATH search space from Node keeps Unicode
111+ // paths as native JS strings and avoids mojibake entirely.
113112 found = scanPath ( name ) ;
113+ } else {
114+ try {
115+ const output = execFileSync ( "which" , [ name ] , {
116+ encoding : "utf-8" ,
117+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
118+ timeout : 5000 ,
119+ } ) ;
120+ const candidate = chooseBestPathCandidate ( name , output . split ( / \r ? \n / ) ) ;
121+ found = candidate && isExecutablePathCandidate ( candidate ) ? candidate : scanPath ( name ) ;
122+ } catch {
123+ found = scanPath ( name ) ;
124+ }
114125 }
115126 found ??= findInProjectLocalBin ( name ) ;
116127 found ??= findInCommonDirs ( name ) ;
@@ -131,8 +142,9 @@ export interface FindFfBinaryOptions {
131142}
132143
133144/**
134- * Resolve an FFmpeg-family binary: env override first, then `which`/`where`,
135- * then a manual PATH scan (covers Windows PATHEXT), a project-local
145+ * Resolve an FFmpeg-family binary: env override first, then a native
146+ * current-directory/PATH scan on Windows or `which` plus PATH scan on Unix,
147+ * then a project-local
136148 * `.hyperframes/bin`, then well-known Unix install dirs. System lookups are
137149 * cached per binary for the process lifetime; the env override is re-read on
138150 * every call.
0 commit comments