tutorial 07: FFMPEG

Welcome to this tutorial on using FFmpeg. In this tutorial, you will learn to process files using FFmpeg. You will also learn how to use various other functions and commands to manipulate strings, display dialog boxes, update the progress bar, check for user cancelation, and more. By the end of this tutorial, you should have a solid understanding of how these functions and commands work and be able to use them to perform similar tasks in your own projects.

Step 1: Implement the cmd_callback() function

To start, create a new recipe file and define the cmd_callback() function. This function will be called after the cmd() function is executed and receives the command name and output as arguments. This function calculates the progress of the ffmpeg command and updates the progress bar. It also checks if the process has been canceled and returns an object to terminate the process if needed.

function cmdcallback(cmdname, cmd_output)
{
    // Check if the command source is "ffmpeg"
    if (cmdname == "ffmpeg" && cmdoutput.length > 0)
    {
        // Calculate progress in seconds from the current timestamp and update progress bar
        var progress = CalculateSeconds(GetStringFromTo(cmd_output, "time=", " bitrate"));
        progress = Math.round(progress / totalsec * 100);
        setProgress(progress);
    }

    return {
        "terminate": isCanceled()
        };
}

Step 2: Implement the GetStringFromTo() function

Next, define the GetStringFromTo() function. This function searches for a string between a specified starting point (from) and ending point (to) in a source string (source). It uses a regular expression to extract the desired string and returns it.

function GetStringFromTo(source, from, to){
    // Create regular expression to match string between 'from' and 'to'
    var wildcard = from + "(.*)" + to;

    // Search for match in source string
    var r = searchRegEx(source, wildcard, "i", 0);

    // If a match is found, return the desired string
    if (typeof(r[0]) == "string") {
        return r[0].substring(from.length, r[0].length - to.length);
    } else {
        return "";
    }
}

Step 3: Implement the CalculateSeconds() function

Now let's implement the CalculateSeconds() function. This function converts a duration in the format HH:MM:SS to the equivalent number of seconds.

function CalculateSeconds(dur_tc)
{
    // Extract hours, minutes, and seconds from duration string
    var hours = (dur_tc.substring(0, 2));
    var minutes = (dur_tc.substring(3, 3+2));
    var seconds = (dur_tc.substring(6, 6+2));
    // Remove leading zeros
    if (hours.substring(0, 1) == "0") {
        hours = hours.substring(1,2);
    }

    if (minutes.substring(0, 1) == "0") {
        minutes = minutes.substring(1,2);
    }

    if (seconds.substring(0, 1) == "0") {
        seconds = seconds.substring(1,2);
    }

    // Convert strings to integers
    hours = parseInt(hours);
    minutes = parseInt(minutes);
    seconds = parseInt(seconds);
    
    // Calculate total number of seconds
    return (hours 60 60) + (minutes * 60) + seconds;
}

Step 4: Implement the getFileSeconds() function

Now let's implement the getFileSeconds() function. This function runs the ffprobe command on a given file (infile) to get its duration and then uses CalculateSeconds() to convert it to the number of seconds.

function getFileSeconds(infile)
{
    // Run ffprobe command on infile
    var probeoutput = cmd("ffprobe", [infile]);

    // Extract duration string from output
    var dur_tc = GetStringFromTo(probeoutput, "Duration: ", ", s");

    // Convert duration string to number of seconds
    return CalculateSeconds(dur_tc);
}

Step 5: Implement the onConfig() function

Now let's define the onConfig() function. This function displays a dialog box to the user to select a container format from a list of options. It stores the selection in the items object.

function onConfig()
{
    // Define options for combobox
    var options = ["mp4","avi","mov"];

    // Create form object
    var form = {
        "extension" : {
            "type" : "combobox",
            "default" : options[0],
            "items" : options,
            "label" : "Select container type:"
            },
        "OK" : {
            "type" : "button",
            "label" : "OK",
            "returns" : 1
            },
        "cancel" : {
            "type" : "button",
            "label" : "cancel",
            "returns" : 0
            }
        };

    // Display dialog box and store result
    var r = dialog(header.title, "Select a container format", "w", form);

    // If user cancels, abort process
    if (r.cancel) {
        abort("cancelled");
        }

    // Store selection in items object
    var items = getItems();
    items.extension = r.extension;
    setItems(items);
};

Step 6: Implement the main() function

Finally, let's implement the main() function. This is the main function that coordinates the rest of the code. It first checks if ffmpeg and ffprobe are installed, then gets a list of files from the user, displays the configuration dialog, and processes each file in the list. It uses cmd() to run ffmpeg commands and cmd_callback() as a callback function. It also updates the progress bar and checks for user cancelation at various points.

var totalsec=0.; // a global var to track the total length in sec of the current file

function main()
{
    // Get current epoch time
    var startEpoch = getCurrentEpoch(); // secs since 1 jan 1970

    // Check if ffmpeg is installed
    if (fileExists(getAllowedApps("ffmpeg")) == false) {
        abort("no ffmpeg installed");
    }

    if (fileExists(getAllowedApps("ffprobe")) == false) {
        abort("no ffprobe installed");
    }

    // Set main message and progress bar
    setMainMessage("starting");
    setProgress(0);

    // Get list of files from user
    var files = getFiles();
    if (files.length <= 0) {
        abort("drop a file in the list to encode");
    }

    // Display configuration dialog
    onConfig();

    // Process each file in the list
    for (i = 0; i < files.length; i++)
    {
        setProgress(101);
        var infile = files[i].path;

        // Get duration of file in seconds
        totalsec = getFileSeconds(infile);
        if (totalsec <= 0) {
            abort("no content found in this file");
        }

        // Set up output path and filename
        var pathinfo = getPathInfo(infile);

        var items = getItems();
        var outfile = pathinfo.folder + gvar.pss + pathinfo.basename + "-[magnetron.app]." + items.extension;

        outfile = saveFileDialog("Select a output path", config.outfile, ".");

        if (outfile.length > 0)
        {
            setMainMessage("busy please wait");

            // Set the ffmpeg arguments
            // the -y option specifies that the output file should be overwritten if it already exists.
            // the -i option is used to specify the input file for the command
            // plus the input and output file paths
            var args = ["-y", "-i", infile, outfile];
            echo(cmd("ffmpeg", args)); // perform cmd

            // check output
            if (getFileBytes(outfile) <= 0)
            setMainMessage("can't encode this file");

            if(isCanceled())
                abort("cancelled");

        }
    }

    // Set progress bar
    setProgress(100);

    // Calculate elapsed time in seconds
    var endEpoch = getCurrentEpoch();
    var elapsed = endEpoch - startEpoch;

    // Display elapsed time in a message box
    setMainMessage("done, elapsed time: " + elapsed + " secs");
}

That's it! You should now have a complete understanding of using FFmpeg in your own recipes. Check /syntax/ for more details on the functions and arguments.