Juan Carlos Farah Juan Carlos Farah - 6 months ago 104
Node.js Question

FFmpeg Fade Out Audio Filter (afade) not being Applied to Track Clipped Iteratively

I am trying to split a track into multiple fixed-sized (30-second) clips, each with a (5-second) fade-in / fade-out at the beginning and end, respectively. I'm using

node-fluent-ffmpeg
to interface with
ffmpeg
and saving each of the
ffmpeg
commands in an array of ES6 Promises that I later execute using
Promise.all()
.

I am able to clip the tracks and add the fade-in filter successfully, but for some reason the fade-out filter is only applied to the first clip of the track. I have looked around for answers both in the
ffmpeg
and
node-fluent-ffmpeg
documentation (here and here), but there is no mention of issues arising from applying fade-out filters to a track that is being clipped multiple times.

My code is very similar to the snippet below, with the audio filters being applied in sequence using the
audioFilters
method. Note that I have tried leaving only the fade-out filter, but the problem persists. Any pointers would be greatly appreciated.

var promises = [];
const duration = track.duration;
const interval = 30;
const fade = 5;
const bitrate = 128;

for (var i = 0; i <= Math.floor(duration) - interval; ++i) {
const start = i; // Start second.
const end = start + interval;
const mp3 = `${new ObjectId().toHexString()}.mp3`;

var command = new Promise((resolve, reject) => {
ffmpeg(path).setStartTime(start)
.audioBitrate(bitrate)
.audioFilters([
{
filter: 'afade',
options: `t=in:ss=${start}:d=${fade}`
},
{
filter: 'afade',
options: `t=out:st=${end - fade}:d=${fade}`
}
])
.duration(interval)
.on('error', (err) => {
reject("An error occurred while clipping.");
})
.on('end', () => {
resolve(`Finished processing ${output}.`);
})
.save(mp3);
});
promises.push(command);
}


And here is my
ffmpeg
version information:

ffmpeg version 2.8.6 Copyright (c) 2000-2016 the FFmpeg developers
built with Apple LLVM version 7.0.2 (clang-700.1.81)
configuration: --prefix=/usr/local/Cellar/ffmpeg/2.8.6 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-opencl --enable-libx264 --enable-libmp3lame --enable-libvo-aacenc --enable-libxvid --enable-vda
libavutil 54. 31.100 / 54. 31.100
libavcodec 56. 60.100 / 56. 60.100
libavformat 56. 40.101 / 56. 40.101
libavdevice 56. 4.100 / 56. 4.100
libavfilter 5. 40.101 / 5. 40.101
libavresample 2. 1. 0 / 2. 1. 0
libswscale 3. 1.101 / 3. 1.101
libswresample 1. 2.101 / 1. 2.101
libpostproc 53. 3.100 / 53. 3.100

Answer

After a while of tinkering around, clipping different tracks with the code above, I figured out the issue.

The Problem

The problem was with how I kept track of the start and end times for the fades. It turns out that the times for the fade were being measured with respect to the 30-second clips, not the original track itself. I, on the other hand, kept increasing the start and end times using the loop below, thinking the fade was applied at the time step in relation to the original track.

for (var i = 0; i <= Math.floor(duration) - interval; ++i) {
    const start = i;            // Start second.      
    const end = start + interval;
    ...

So by updating the start and end values with the loop, I was going beyond the end of the 30-second clip when I applied the audio filters.

.audioFilters([
    {
        filter: 'afade',
        options: `t=in:ss=${start}:d=${fade}`
    },
    {
        filter: 'afade',
        options: `t=out:st=${end - fade}:d=${fade}`
    }
])

That is why I could only hear the fade out for the first clips. The fade-in was not working either, but it was harder to notice, because the first few clips would have a fade in that sounded natural (even though it was also moving up the track and eventually not being applied).

The Solution

Turns out it was super simple. I just needed to replace the values in the options key so that the start for the fade in was always 0 and the start for the fade out was always the interval length (30) minus the fade duration (3).

.audioFilters([
    {
        filter: 'afade',
        options: `t=in:ss=0:d=${fade}`
    },
    {
        filter: 'afade',
        options: `t=out:st=${interval - fade}:d=${fade}`
    }
])

It was a simple issue in the end. Nevertheless, it's good to point out that if you're clipping and applying a fade at the same time, you need to make sure your times are with regards to the clipped track, not the original one.