Instagram video clip
Instagram has length and size restrictions for their videos. This recipe crops videos into the Instagram's square format and cuts them into 60-second reels or 15-second stories.
Download this recipe from the extended collection here:
https://magnetron.app/recipes/instagram-video-clip
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 complete source below:
(added Aug 2023)
header={
"recipe_version": "1.10",
"title": "Instagram video",
"description": "Convert a video to the instagram share screen ratio and time limit",
"category": "video",
"chef": "BeatRig",
"spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=",
"palette": "Velvet Cake",
"flavour": "lEM7evufJIDvc6sefJlH8F+Uqr5hrIMUpiZ5sJ3x5Mp/ByprgaufRakE0HAm5qH7suEok0tlL24HM8sNWbaV1nxTR4hxkveG7qisQ+yj1ZZTJeB/UccN19lqiMNCDDT1Wp0x8yGcr9BJUzysp05kY1ypS85rT0yVcO1WPViyoiE=",
"time": 1694382987,
"core_version": "0.5.7",
"magnetron_version": "1.0.261",
"type": "default",
"os": "windows,macOS",
"cmds": "ffprobe,ffmpeg",
"functions": "main",
"dependencies": "ffprobe,ffmpeg",
"tags": "video,instagram,square",
"uuid": "d01828bc987346378739461d75cae89a",
"instructions": "Drop file(s) to crop them to a square aspect ratio and cut to a stories or reels length"
};
//----------------------------------------------------------------------------
var config = {
"scale": 1,
"framerate": 0,
"makeMono": false,
"crf": 40,
"parts": 1,
"makeMono": true,
"partItt": 0,
"cutLength": 0,
"sizeRatio": 1,
"totalsec": 0,
"fps": 0,
"maxItt": 4,
"maxSize": 4 * 1024 * 1024 * 1024, // to 4 gb
"mincrf": 20,
"maxcrf": 55,
"minframerate": 10,
"maxframerate": 25,
"minScale": 1,
"maxScale": 4,
};
//----------------------------------------------------------------------------
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.cutLength /*config.totalsec*/ * 100);
setProgress(progress);
}
return {
"terminate": isCanceled(),
"input": "" // send console input
};
}
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 getFileProps(infile)
{
var probeoutput = cmd("ffprobe", [infile]);
echo(probeoutput);
var dur_tc = findSubString(probeoutput, "Duration: ", ", s");
config.totalsec = calculateSeconds(dur_tc);
config.fps = parseInt(findSubString(probeoutput, "kb/s, ", " fps"));
echo("dur:" + config.totalsec);
echo("fps:" + config.fps);
return dur_tc;
}
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("-vcodec");
args1.push("libx264");
if (config.framerate > 0)
{
echo("frame rate:" + config.framerate);
args1.push("-r");
args1.push(config.framerate);
}
echo("compression:" + config.crf);
args1.push("-crf");
args1.push(config.crf);
args1.push("-preset");
args1.push("slow");
args1.push("-c:a");
args1.push("aac");
if (config.makeMono)
{
echo("mono");
args1.push("-ac");
args1.push("1");
}
args1.push("-movflags");
args1.push("+faststart");
{
args1.push("-filter_complex");
var filterArg="[0]crop=min(iw\\,ih):min(iw\\,ih),format=yuv420p[out1]";
if (gvar.isWindows) // needs quotes
filterArg = "\"" + filterArg + "\"";
args1.push(filterArg);
echo("filter:" + filterArg);
// map the inputs
args1.push("-map");
args1.push("[out1]");
args1.push("-map");
args1.push("0:a?");
}
args1.push(config.outfile);
return args1;
}
//----------------------------------------------------------------------------
function setOutputSettings()
{
var partRatio = config.sizeRatio / config.parts;
config.makeMono = (partRatio > 1.5);
config.crf = config.mincrf;
// some size ratio based presets
if (partRatio > 8)
{
config.scale = config.maxScale;
config.framerate = config.minframerate;
}
else if (partRatio > 4)
{
config.scale = 2;
config.framerate = Math.min(Math.max(parseInt(config.fps / (partRatio / config.scale)), config.minframerate), config.maxframerate);
}
else if (partRatio > 2)
config.scale = 2;
}
//----------------------------------------------------------------------------
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();
config.tag = "-[insta]";
var numFiles = files.length;
for (i = 0; i < Math.max(numFiles, 1); i++)
{
// base path
var infile = numFiles > 0 ? files[i].path : "";
var filesize = getFileBytes(infile);
var infileinfo = getPathInfo(numFiles > 0 ? infile : folders.output);
var ouputFolderPath = numFiles > 0 ? infileinfo.folder : folders.output;
echo(infile);
echo("file size:" + bytesToDescription(filesize));
setFileIcon(files[i].path, files[i].index, "HourglassHalf");
setFileIconColor(files[i].path, files[i].index, "FF5299D3");
setFileStatus(files[i].path, files[i].index, "busy");
var maxClipLength = 60; // limits 15 sec or 60 sec
var options = ["15 sec Stories","60 sec Reels"];
config.videotype = options[1];
var dialog_width = 600;
form1 = {
"header" : {
"type" : "text",
"label" : "This process will convert videos to instagram spec",
"just" : "l",
"bounds" : {
"x": 10,
"y" : 5,
"w" : dialog_width,
},
},
"file1_header" : {
"type" : "text",
"label" : "source file",
"just" : "l",
"bounds" : {
"w" : 120,
},
},
"inputfile" : {
"type" : "fileselect",
"path" : infile,
"editable" : false,
"dir" : false,
"saving" : false,
"label" : "source file",
"bounds" : {
"x" : 130,
"y" : -1,
"w" : dialog_width - 130,
},
},
"param_header" : {
"type" : "text",
"label" : "Video type",
"just" : "l",
"bounds" : {
"w" : 120,
},
},
"maxClipLength" : {
"type" : "combobox",
"default" : config.videotype,
"items" : options,
"label" : "Select length:",
"bounds" : {
"x" : 130,
"y" : -1,
"w" : dialog_width - 130,
}
},
"file2_header" : {
"type" : "text",
"label" : "output folder",
"just" : "l",
"bounds" : {
"w" : 120,
},
},
"outputfolder" : {
"type" : "fileselect",
"path" : ouputFolderPath,
"editable" : false,
"dir" : true,
"saving" : true,
"label" : "select output folder",
"bounds" : {
"x" : 130,
"y" : -1,
"w" : dialog_width - 130,
},
},
"tag" : {
"type" : "textedit",
"default" : config.tag,
"label" : "add file name tag:",
"bounds" : {
"x" : dialog_width/2,
"y" : -1,
"w" : dialog_width/2
}
},
"okay" : {
"type" : "button",
"label" : "Start",
"bounds" : {
"w" : dialog_width/2 - 10,
},
"returns" : 1
},
"cancel" : {
"type" : "button",
"label" : "cancel",
"bounds" : {
"y": -1,
"x": dialog_width/2 + 10,
"w" : dialog_width/2 - 10
},
"returns" : 0
},
};
var r = dialog(form1);
config.cutLength = 60;
if (r.maxClipLength == options[0])
config.cutLength = 15;
config.videotype = r.maxClipLength;
config.tag = r.tag;
infile = r.inputfile;
var pathinfo = getPathInfo(infile);
var ouputFolderPath = numFiles > 0 ? infileinfo.folder : folders.output;
if (r.okay == 1)
{
getFileProps(infile); // calc the duration in sec
if (config.totalsec <= 0)
abort("no content found in this file");
config.parts = (config.totalsec / config.cutLength);
if (parseInt(config.parts) < config.parts)
config.parts = parseInt(config.parts) + 1;
else config.parts = parseInt(config.parts);
setOutputSettings(); // compression setup
config.framecount = config.fps * config.totalsec;
echo("framecount:" + config.framecount); // for progress calculation
setProgress(101);
var newfilesize = 0;
config.partItt = 0;
while (config.partItt < config.parts)
{
if (config.parts > 1)
{
config.outfile = r.outputfolder + gvar.pss + pathinfo.basename + r.tag + "-[" + (config.partItt + 1) + "_" + config.parts + "]." + pathinfo.ext;
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" + config.outfile, "w", form2).cancel == 1)
abort("cancelled");
}
}
else config.outfile = r.outputfolder + gvar.pss + pathinfo.basename + r.tag + "." + pathinfo.ext;
var msg = ("busy please wait");
setMainMessage(msg);
echo(msg);
var args1 = setupArgs(infile, config.partItt);
echo(cmd("ffmpeg", args1)); // perform cmd
newfilesize = getFileBytes(config.outfile);
echo("new file size:" + bytesToDescription(newfilesize));
if (newfilesize <= 0)
config.partItt = config.parts; // stop
if(isCanceled())
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 i = numFiles; // end-loop
}
var then = getCurrentEpoch(); // secs since 1 jan 1970
setMainMessage("done in " + (then - now) + " sec");
echo(objectToString(config));
setProgress(100);
}
function dialog_callback(props)
{
if (props["name"] == "inputfile")
{
var info = getPathInfo(props["path"]);
var newprops = { "outputfolder" : { "path" : info.folder } };
dialog(newprops);
}
}