@@ -77,6 +77,17 @@ export class FFmpegApi {
7777 this . ffmpeg . terminate ( ) ;
7878 }
7979
80+ private async exec ( args : string [ ] ) {
81+ if ( this . _enableLogging ) {
82+ console . log (
83+ 'Running' ,
84+ [ 'ffmpeg' , ...args . map ( ( arg ) => ( arg . includes ( ' ' ) ? `"${ arg } "` : arg ) ) ] . join ( ' ' ) ,
85+ args
86+ ) ;
87+ }
88+ return await this . ffmpeg . exec ( args ) ;
89+ }
90+
8091 async probe ( file : File | string ) : Promise < FFprobeOutput > {
8192 const fileName = file instanceof File ? file . name : file ;
8293 const jsonOutputFile = `${ fileName } -ffprobe.json` ;
@@ -107,10 +118,8 @@ export class FFmpegApi {
107118 }
108119
109120 await this . useMountedFile ( file , ( mountedFilePath ) => {
110- return this . ffmpeg . exec ( [
121+ return this . exec ( [
111122 ...[ '-loglevel' , 'info' ] ,
112- // Always overwrite files
113- '-y' ,
114123 ...[ '-i' , mountedFilePath ] ,
115124 ...audioStreamIds . flatMap ( ( audioStreamId ) => [
116125 // Extract playable audio stream as WAV (16-bit, little endian)
@@ -152,11 +161,11 @@ export class FFmpegApi {
152161
153162 const reencode = trimming ?. highPrecision ?? false ;
154163
164+ const shouldApplySingleAudio = audio . singleOutputStream && audio . streams . length >= 2 ;
165+
155166 await this . useMountedFile ( file , ( mountedFilePath ) => {
156- return this . ffmpeg . exec ( [
167+ return this . exec ( [
157168 ...[ '-loglevel' , 'info' ] ,
158- // Always overwrite files
159- '-y' ,
160169
161170 ...[ '-i' , mountedFilePath ] ,
162171
@@ -166,14 +175,26 @@ export class FFmpegApi {
166175 // Include video
167176 ...( includeVideo ? [ '-map' , '0:v?' ] : [ ] ) ,
168177
169- // Include audio streams
170- ...audio . streams . flatMap ( ( { id : audioStreamId } ) => [ '-map' , `0:a:${ audioStreamId } ` ] ) ,
171-
172178 // Mix audio streams into a single output stream
173- ...( audio . singleOutputStream && audio . streams . length >= 2
179+ ...( shouldApplySingleAudio
174180 ? [
181+ // Include only relevant audio streams
182+ ...audio . streams . flatMap ( ( { id : audioStreamId } ) => [ '-map' , `0:a:${ audioStreamId } ` ] ) ,
183+
175184 '-filter_complex' ,
176- `amix=inputs=${ audio . streams . length } :dropout_transition=0:normalize=0`
185+ `amix=inputs=${ audio . streams . length } :weights="${ this . createFFmpegAmixWeights ( audio . streams ) } ":dropout_transition=0:normalize=0`
186+ ]
187+ : [ ] ) ,
188+
189+ // Adjust audio stream volume separately
190+ ...( ! shouldApplySingleAudio && audio . streams . length > 0
191+ ? [
192+ '-filter_complex' ,
193+ audio . streams
194+ . map ( ( { id, volume } ) => `[0:a:${ id } ] volume=${ volume } [a${ id } ]` )
195+ . join ( '; ' ) ,
196+
197+ ...audio . streams . flatMap ( ( { id } ) => [ '-map' , `[a${ id } ]` ] )
177198 ]
178199 : [ ] ) ,
179200
@@ -197,6 +218,10 @@ export class FFmpegApi {
197218 } ;
198219 }
199220
221+ private createFFmpegAmixWeights ( streams : AudioStreamInput [ ] ) {
222+ return streams . map ( ( stream ) => stream . volume . toFixed ( 3 ) ) . join ( ' ' ) ;
223+ }
224+
200225 private getFFmpegCodecOptionsForFormat ( format : ValidFormat , options : ConvertOptions ) : string [ ] {
201226 switch ( format ) {
202227 case 'opus' :
0 commit comments