2

I am trying to use fluent-ffmpeg with my electron app to concatenate multiple audio files together with an image in a video. So if i have three files:

song1.mp3 1:00 song2.mp3 0:30 song3.mp3 2:00 front.jpg

I could create output.mp4 which would be 3:30 seconds long, and play each file one after the other in order. With front.jpg set as the background image.

I am trying to create the concatenated audio file first for this video, then I can just render a vid with two inputs; image and the 3:30second long concatenated audio file. But I'm having difficulty getting my electron app to wait for the ffmpeg job to run and complete.

I know how to do all of these ffmpeg jobs on the command-line, but I've been following this guide for how to package ffmpeg into an electron app that can run on mac/win10/linux environments. I'm developing it on win10 right now. gur.com/LtykP.png

I have a button: <button onClick='fullAlbum("upload-${uploadNumber}")'>FULLALBUM</button>

that when I click runs the fullAlbum() function that calls combineMp3FilesOrig to run the actual ffmpeg job:

async function fullAlbum(uploadName) {
    //document.getElementById("buttonId").disabled = true;

    //get table
    var table = $(`#upload_${uploadNumber}_table`).DataTable()
    //get all selected rows
    var selectedRows = table.rows( '.selected' ).data()
    //get outputFile location
    var path = require('path');
    var outputDir = path.dirname(selectedRows[0].audioFilepath)
    //create outputfile
    var timestamp = new Date().getUTCMilliseconds();
    let outputFilepath = `${outputDir}/output-${timestamp}.mp3` 

    
    console.log('fullAlbum() button pressed: ', timestamp)

    await combineMp3FilesOrig(selectedRows, outputFilepath, '320k', timestamp);
    //document.getElementById("buttonId").disabled = false;

    console.log(`fullAlbum() /output-${timestamp}.mp3 should be created now`)
}

function combineMp3FilesOrig(selectedRows, outputFilepath, bitrate, timestamp) {
    console.log(`combineMp3FilesOrig(): ${outputFilepath}`)
    
    //begin get ffmpeg info
    const ffmpeg = require('fluent-ffmpeg');
    //Get the paths to the packaged versions of the binaries we want to use
    const ffmpegPath = require('ffmpeg-static').replace('app.asar','app.asar.unpacked');
    const ffprobePath = require('ffprobe-static').path.replace('app.asar','app.asar.unpacked');
    //tell the ffmpeg package where it can find the needed binaries.
    ffmpeg.setFfmpegPath(ffmpegPath);
    ffmpeg.setFfprobePath(ffprobePath);
    //end set ffmpeg info

    //create ffmpeg command
    console.log(`combineMp3FilesOrig(): create command`)
    const command = ffmpeg();
    //set command inputs
    command.input('C:\\Users\\marti\\Documents\\martinradio\\uploads\\CharlyBoyUTurn\\5. Akula (Club Mix).flac')
    command.input('C:\\Users\\marti\\Documents\\martinradio\\uploads\\CharlyBoyUTurn\\4. Civilian Barracks.flac')

    return new Promise((resolve, reject) => {
        console.log(`combineMp3FilesOrig(): command status logging`)
        command.on('progress', function(progress) {
            console.info(`Processing : ${progress.percent} % done`);
        })
        .on('codecData', function(data) {
            console.log('codecData=',data);
        })
        .on('end', function() {
            console.log('file has been converted succesfully; resolve() promise');
            resolve();
        })
        .on('error', function(err) {
            console.log('an error happened: ' + err.message, ', reject()');
            reject(err);
        })
        console.log(`combineMp3FilesOrig(): add audio bitrate to command`)
        command.audioBitrate(bitrate)
        console.log(`combineMp3FilesOrig(): tell command to merge inputs to single file`)
        command.mergeToFile(outputFilepath);
        console.log(`combineMp3FilesOrig(): end of promise`)
    });
    console.log(`combineMp3FilesOrig(): end of function`)
}

When I click my button once, my console.logs show the promise is entered, the command is created, but the function just ends without waiting for a resolve(); Waiting a couple minutes doesnt change anything.

enter image description here

If I press the button again:

enter image description here

A new command gets created, reaches the end of the promise, but this time actually starts, and triggers the previous command to start. Both jobs then run and their files are rendered at the correct length (12:08) and the correct quality (320k)

Is there something with my promise I need to fix involving async functions and promises in an electron app? I tried editing my ffmpeg command to include

command.run()

At the end of my promise to ensure it gets triggered; but that leads to an err in console saying Uncaught (in promise) Error: No output specified because apparently in fluent-ffmpeg command.mergeToFile(outputFilepath); isnt good enough and I need to include .output(outputFilepath) as well. If I change command.run() to command.output(outputFilepath).run(), when i click my button, the ffmpeg job gets triggered and rendered perfectly fine. EXCEPT THAT THE FILE IS ALWAYS 128kbps

So I'm trying to figure out why my included code block, my ffmpeg command doesn't run the first time when its created.

Martin
  • 1,336
  • 4
  • 32
  • 69

1 Answers1

0

I've played about with this and I'm seeing the same issue with your original code, the file is being output with 128k bitrate.

This code seems to work, though the max bitrate I'm getting is 320k (I presume this is a limitation of the codec).

After testing again I think I'm getting the same behaviour as you are, in that the file takes some time to generate. If I return a Promise from the combineMp3FilesOrig function, then await in the click handler. I disable the button until the call is complete, obviously your button id will be different.

function combineMp3FilesOrig(selectedRows, outputFile, bitrate) {
    const command = ffmpeg();
    var count = selectedRows.length;
    for(var i = 0; i < count; i++){
        command.input(selectedRows[i].audioFilepath)
    }   
    return new Promise((resolve, reject) => { 
        command.on('progress', function(progress) {
            console.info(`Processing : ${progress.percent} % done`);
        })
        .on('codecData', function(data) {
            console.log('codecData=',data);
        })
        .on('end', function() {
            console.log('file has been converted succesfully');
            resolve();
        })
        .on('error', function(err) {
            console.log('an error happened: ' + err.message);
            reject(err);
        }).audioBitrate(bitrate).mergeToFile(outputFile);
    });
}

async function convertFiles() {
    document.getElementById("buttonId").disabled = true;
    await combineMp3FilesOrig(selectedRows, 'output.mp3', '320k');
    document.getElementById("buttonId").disabled = false;
}
Terry Lennox
  • 29,471
  • 5
  • 28
  • 40
  • 1
    i tested this code and was able to get it to generate the 320kb file, but only when my function gets called twice for some reason (updated my code to show ) – Martin Sep 11 '20 at 00:26
  • Hmmm, that's interesting, I'll see if I can replicate and get back to you! – Terry Lennox Sep 11 '20 at 05:34
  • I can replicate the issue now, from what I can see, it's taking maybe 5 seconds to generate the new file, so I added a Promise wrapper around the conversion function, then use await to wait for the processing to complete. – Terry Lennox Sep 11 '20 at 07:24
  • 1
    Ive been trying to get the code block you posted working but I haven't been able to get my ffmpeg mergeToFile command able to run the first time when its called. If I include `command.run()` to tell the command to run before the promise ends it works great, but the file is always 129kbps horrible quality. I've updated my question – Martin Sep 12 '20 at 05:13
  • That's very frustrating to be sure. I don't think I'm able to replicate the exact behaviour. Have you tried with smaller files? Perhaps you might see a different result that would help you diagnose the issue. – Terry Lennox Sep 12 '20 at 09:01
  • im thinking it might just be something with my local electron dev environment, im going to try packaging my app and seeing if the issue persists, i just did that on windows and it seemed to work, going to do some more testing – Martin Sep 12 '20 at 20:52