1+ import ffbinaries from 'ffbinaries' ;
2+ import path from 'node:path' ;
3+ import fs from 'node:fs/promises' ;
4+ import { chmod } from 'node:fs/promises' ;
5+
6+ // Define the output directory
7+ const dest = path . join ( import . meta. dirname , '../src-tauri/binaries' ) ;
8+
9+ // Mapping: ffbinaries platform code -> Tauri target triple filename
10+ const platformMap : Record < string , string > = {
11+ 'windows-64' : 'ffmpeg-x86_64-pc-windows-msvc.exe' ,
12+ 'osx-64' : 'ffmpeg-x86_64-apple-darwin' ,
13+ 'linux-64' : 'ffmpeg-x86_64-unknown-linux-gnu' ,
14+ 'linux-arm64' : 'ffmpeg-aarch64-unknown-linux-gnu' ,
15+ } ;
16+
17+ async function main ( ) {
18+ await fs . mkdir ( dest , { recursive : true } ) ;
19+ console . log ( '🚀 Starting FFmpeg download...' ) ;
20+
21+ // 1. Download for each platform in parallel
22+ const downloadPromises = Object . entries ( platformMap ) . map ( async ( [ platformCode , targetFilename ] ) => {
23+ return downloadAndRename ( platformCode as ffbinaries . Platform , targetFilename ) ;
24+ } ) ;
25+
26+ await Promise . all ( downloadPromises ) ;
27+
28+ // 2. Handle M1/M2/M3 Mac (Copy Intel to ARM)
29+ await copyIntelToArm ( ) ;
30+
31+ console . log ( '🎉 Done! FFmpeg binaries are ready in src-tauri/binaries/' ) ;
32+ }
33+
34+ async function downloadAndRename ( platform : ffbinaries . Platform , targetFilename : string ) {
35+ const tempDir = path . join ( dest , `_temp_${ platform } ` ) ;
36+ await fs . mkdir ( tempDir , { recursive : true } ) ;
37+
38+ return new Promise < void > ( ( resolve , reject ) => {
39+ ffbinaries . downloadBinaries ( [ 'ffmpeg' ] , {
40+ destination : tempDir ,
41+ platform : platform
42+ } , async ( err ) => {
43+ if ( err ) {
44+ await fs . rm ( tempDir , { recursive : true , force : true } ) . catch ( ( ) => { } ) ;
45+ return reject ( err ) ;
46+ }
47+
48+ try {
49+ // [CRITICAL CHANGE] Hunt for the actual binary file recursively
50+ const binaryName = platform . startsWith ( 'windows' ) ? 'ffmpeg.exe' : 'ffmpeg' ;
51+ const foundBinaryPath = await findFileRecursively ( tempDir , binaryName ) ;
52+
53+ if ( ! foundBinaryPath ) {
54+ throw new Error ( `Binary ${ binaryName } not found in extracted folder for ${ platform } ` ) ;
55+ }
56+
57+ const finalPath = path . join ( dest , targetFilename ) ;
58+
59+ // Nuke the destination if it exists (file OR folder)
60+ await fs . rm ( finalPath , { recursive : true , force : true } ) ;
61+
62+ // Move the specific file found, not the folder
63+ await fs . rename ( foundBinaryPath , finalPath ) ;
64+ console . log ( ` • Ready: ${ targetFilename } ` ) ;
65+
66+ if ( ! targetFilename . endsWith ( '.exe' ) ) {
67+ await chmod ( finalPath , 0o755 ) ;
68+ }
69+ } catch ( e ) {
70+ reject ( new Error ( `Failed to process ${ platform } : ${ e } ` ) ) ;
71+ } finally {
72+ // Cleanup temp folder
73+ await fs . rm ( tempDir , { recursive : true , force : true } ) . catch ( ( ) => { } ) ;
74+ }
75+
76+ resolve ( ) ;
77+ } ) ;
78+ } ) ;
79+ }
80+
81+ // Recursive helper to find the file even if nested in subfolders
82+ async function findFileRecursively ( dir : string , filename : string ) : Promise < string | null > {
83+ const entries = await fs . readdir ( dir , { withFileTypes : true } ) ;
84+
85+ for ( const entry of entries ) {
86+ const fullPath = path . join ( dir , entry . name ) ;
87+
88+ // If we found the specific file we want
89+ if ( entry . isFile ( ) && entry . name === filename ) {
90+ return fullPath ;
91+ }
92+
93+ // If directory, dive in
94+ if ( entry . isDirectory ( ) ) {
95+ const found = await findFileRecursively ( fullPath , filename ) ;
96+ if ( found ) return found ;
97+ }
98+ }
99+ return null ;
100+ }
101+
102+ async function copyIntelToArm ( ) {
103+ const intelMacPath = path . join ( dest , 'ffmpeg-x86_64-apple-darwin' ) ;
104+ const armMacPath = path . join ( dest , 'ffmpeg-aarch64-apple-darwin' ) ;
105+
106+ try {
107+ try {
108+ await fs . access ( armMacPath ) ;
109+ return ;
110+ } catch { /* proceed */ }
111+
112+ await fs . access ( intelMacPath ) ;
113+ await fs . rm ( armMacPath , { recursive : true , force : true } ) ; // Ensure clean target
114+ await fs . copyFile ( intelMacPath , armMacPath ) ;
115+ console . log ( ` • Copied Intel binary to ${ path . basename ( armMacPath ) } (Runs via Rosetta)` ) ;
116+ } catch ( e ) {
117+ // Intel file likely missing
118+ }
119+ }
120+
121+ main ( ) . catch ( console . error ) ;
0 commit comments