Skip to content

Commit 1b4504f

Browse files
committed
Add ffmpeg parameters to adjust volume of audio streams
1 parent 3765c84 commit 1b4504f

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

src/lib/components/Editor/ExportDialog/ExportDialog.svelte

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
3030
let isTrimmingActive = $derived(markingRange.start > 0 || markingRange.end < 1);
3131
let clipDuration = $derived((markingRange.end - markingRange.start) * videoDuration);
32-
let activeTracks = $derived(tracks.filter((track) => track.isUsed));
32+
let activeTrackCount = $derived(tracks.filter((track) => track.isUsed).length);
3333
3434
let phase = $state<Phase>({ type: 'configuring' });
3535
@@ -51,7 +51,9 @@
5151
? { outputFormat: settings.videoFormat, includeVideo: true }
5252
: { outputFormat: settings.audioFormat, includeVideo: false },
5353
audio: {
54-
streams: activeTracks.map((track, id) => ({ id, volume: track.volume })),
54+
streams: tracks
55+
.map((track, id) => (track.isUsed ? { id, volume: track.volume } : null))
56+
.filter((stream) => stream !== null),
5557
singleOutputStream:
5658
!outputFormat.supportsMultipleAudioStreams || settings.singleAudioOutputStream
5759
},
@@ -103,7 +105,7 @@
103105
{#if phase.type === 'configuring'}
104106
<ContentSettings
105107
clipDurationInSeconds={clipDuration}
106-
trackCount={activeTracks.length}
108+
trackCount={activeTrackCount}
107109
supportsMultipleAudioStreams={outputFormat.supportsMultipleAudioStreams}
108110
/>
109111
{:else if phase.type === 'exporting'}

src/lib/ffmpeg/ffmpeg-api.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)