Cut video X/Twitter

Don't let the 2-minute, 20-second limit stop you from sharing longer videos on default accounts at x/twitter.

This recipe splits up a video to the x/twitter file share time limit. To solve the challenge of sharing longer video files on any account.

no more pop-ups; "Your video is too long. Try to keep it 140 seconds or less"

Download the recipe from the extended collection here:
https://magnetron.app/recipes/cut-video-x-twitter/

For Windows & macOS

You can run this recipe with a magnetron.app license key.
With magnetron.dev you can also use the source to create your own custom processes.

Take a closer look at the source below:

(added Sept 2023)

header={
  "recipe_version": "1.9",
  "title": "Cut video X/Twitter",
  "description": "Cut a video into 2:20 pieces for easy sharing",
  "category": "video",
  "chef": "BeatRig",
  "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=",
  "palette": "Blueberry Slate",
  "flavour": "1ozXUgOnXRCut+6SM2W39uw+zNUj+t8Xg3STQ+GgYxhtH4qAJwU0UaahzrcDVAjScnXgDeIZh/kYEZkY6w2r7p4zQKoUIajUUxNIcIYfz1SjhOxgLhXVEwT66XydUhOgx8YIiQs7HbuYEtvD8Qnfi2V9qZhlcS+bO5Z/lL0S8wI=",
  "time": 1694478580,
  "core_version": "0.5.7",
  "magnetron_version": "1.0.261",
  "cmds": "ffprobe,ffmpeg",
  "type": "default",
  "os": "windows,macOS",
  "functions": "main,onConfig,onAbout",
  "dependencies": "ffprobe,ffmpeg",
  "uuid": "514f947bc71d49e5bbf433f6f4922b7c",
  "instructions": "Drop files and press run to cut videos into 2:20 min parts",
  "tags": "video,share limit,2m20,@:20,cut video"
};


//----------------------------------------------------------------------------
var config = {
    "parts": 1,
    "partItt": 0,

    "cutLength": 140,
    "totalsec": 0,
};

function cmd_callback(cmd_name, cmd_output)
{
    if(cmd_name == "ffmpeg" && cmd_output.length > 0)
    {
        var progress = calculateSeconds(findSubString(cmd_output, "time=", " bitrate"));
        progress = Math.round(progress / config.totalsec * 100);        
        setProgress(progress);
        setMainMessage(progress + "%");
    }
    return {
        "terminate": isCanceled(),
    };
}

function calculateSeconds(dur_tc)
{
    var hours = (dur_tc.substring(0, 2));
    var minutes = (dur_tc.substring(3, 3+2));
    var seconds = (dur_tc.substring(6, 6+2));
    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);
    hours = parseInt(hours);
    minutes = parseInt(minutes);
    seconds = parseInt(seconds);
    
    totalsec = (hours * 60 * 60) + (minutes * 60) + seconds;
    return totalsec;
}

function setDur(infile)
{
    var probeoutput = cmd("ffprobe", [infile]);
    echo(probeoutput);
    
    var dur_tc = findSubString(probeoutput, "Duration: ", ", s");    
    config.totalsec = calculateSeconds(dur_tc);        
    echo("dur:" + config.totalsec);
}

function setupArgs(infile, cutItt)
{
    var args1 = ["-y", "-i", infile];
    
    if (config.parts > 1)
    {
        echo("start time:" + cutItt * config.cutLength);
        args1.push("-ss");
        args1.push(cutItt * config.cutLength);
    }
    if (cutItt < config.parts - 1)
    {
        echo("length:" + config.cutLength);
        args1.push("-t");
        args1.push(config.cutLength);
    }
               
    args1.push("-preset");
    args1.push("slow");

    args1.push("-movflags");
    args1.push("+faststart");

    args1.push(config.outfile);

    return args1;
}

//----------------------------------------------------------------------------
function main()
{
    // check if ffmpeg is installed
    if (fileExists(getAllowedApps("ffmpeg")) == false)
        abort("no ffmpeg installed");    
    if (fileExists(getAllowedApps("ffprobe")) == false)
        abort("no ffprobe installed");    

    setMainMessage("starting");
    setProgress(0);

    var now = getCurrentEpoch(); // secs since 1 jan 1970    
    var files = getFiles();
    var infile = "";
    
    var numFiles = files.length;
    if (numFiles<= 0)
        abort("drop some files first");
    for (i = 0; i < numFiles && isCanceled() == false; i++)
    {
        // base path
        infile = files[i].path;
        var pathinfo = getPathInfo(infile);

        setFileIcon(files[i].path, files[i].index, "HourglassHalf");
        setFileIconColor(files[i].path, files[i].index, "FF5299D3");
        setFileStatus(files[i].path, files[i].index, "busy");

        // calc the duration in sec
        setDur(infile);
        
        if (config.totalsec > 0)
        {
            config.parts = (config.totalsec / config.cutLength);
            if (parseInt(config.parts) < config.parts)
                config.parts = parseInt(config.parts) + 1;
            else config.parts = parseInt(config.parts);

            setProgress(101);
        
            var userOuputPath = pathinfo.folder + gvar.pss + pathinfo.basename + "-x-[magnetron.app]." + pathinfo.ext;

            var fileNames = [];
            if (config.parts > 1)
            { // create all file names (cluster dialogs together)
                config.partItt = 0;           
                while (config.partItt < config.parts)
                { 
                    var pathinfoOut = getPathInfo(userOuputPath);
                    config.outfile = pathinfoOut.folder + gvar.pss + pathinfoOut.basename + "-[" + (config.partItt + 1) + "_" + config.parts + "].mp4";
                    if (fileExists(config.outfile))
                    {
                        var form2 = {
                            "overwrite" : {
                                "type" : "button",
                                "label" : "overwrite",
                                "returns" : 1
                            },
                            "cancel" : {
                                "type" : "button",
                                "label" : "cancel",
                                "returns" : 0
                            }
                        };
                        if (dialog(header.title, "file already exists\n" + pathinfoOut.basename + "." + pathinfoOut.ext, "w", form2).cancel == 1)
                        {
                            setMainMessage("cancelled");
                            abort("cancelled");
                        }
                    }
                    fileNames.push(config.outfile);
                    
                    config.partItt++;
                }
            }
            else
            {
                config.outfile = userOuputPath;
                fileNames.push(config.outfile);
            }

            var newfilesize = 0;
            config.partItt = 0;
            while (config.partItt < config.parts)
            { // main process
                var msg = ("busy please wait");
                setMainMessage(msg);
                echo(msg);
                
                config.outfile = fileNames[config.partItt];
                var args1 = setupArgs(infile, config.partItt);

                echo(cmd("ffmpeg", args1)); // perform cmd

                newfilesize = getFileBytes(config.outfile);
                echo("new file size:" + bytesToDescription(newfilesize));

                if(isCanceled())
                {
                    setMainMessage("cancelled");
                    abort("cancelled");
                }
                config.partItt++;
            }

            if (newfilesize <= 0)
		    {
                setFileIcon(files[i].path, files[i].index, "ExclamationTriangle");
                setFileIconColor(files[i].path, files[i].index, "FFb11414");
                setFileStatus(files[i].path, files[i].index, "can't encode this file", "w");
            }
            else
            {
                setFileIcon(files[i].path, files[i].index, "CheckCircleO");
                setFileIconColor(files[i].path, files[i].index, "FF3aac4d");
                setFileStatus(files[i].path, files[i].index, "done");
            }
        }
        else setFileStatus(files[i].path, files[i].index, "failed to read");
    }

    var then = getCurrentEpoch(); // secs since 1 jan 1970    
    setMainMessage("done in " + (then - now) + " sec");
    echo(objectToString(config));
    setProgress(100);
}

function onAbout()
{
    dialog(header.title, header.description + "\n\nby " + header.chef, "i" );
}

function onConfig()
{
    dialog(header.title, "No config needed. This recipe will convert video files from the file list.");
}

function dialog_callback(props)
{
}