header={ "chef": "BeatRig", "dependencies": "ffprobe,ffmpeg", "title": "EBU R128 - General TV Broadcast", "description": "Validate or fix a file to match the EBU R128 loudness standard, used for general TV broadcast", "instructions": "Drop file(s) here", "recipe_version": "1.67", "tags": "audio, tv, broadcast, loudness, r128", "type": "demo", "os": "windows,macOS", "palette": "Clean Slate", "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=", "flavour": "8K7Hl+VYVAEQ/iUsz7Y3K/9gAYNjWMMNdU7aY8Eufti4+PybuxKVyZDCupSyOqt//qcjKpHkl/7WzHr3NiIQrWMZYAfxsFY/TBcMBvHb3gVI4HUx5E+fdrN47HQK8BW0ffi4wGt6512gNrFaYLTvFBySqUR1MVxsHxnpqiIQMFE=", "time": 1735135804, "core_version": "0.7.2", "magnetron_version": "1.0.333", "functions": "main,onConfig,onAbout", "uuid": "3cdfef0dee2442499505143fdc367d32" }; // ============================================================================= // GLOBALS var settings = {}; // SETTINGS var settingsFile = ""; // LOCATION OF THE SETTINGS FILE var fileTypes = []; // ALLOWED FILE TYPES var has_ffmpeg = false; // CHECK IF FFMPEG & FFPROBE ARE AVAILABLE FOR TRUE PEAK LIMITING var has_sox = false; // CHECK IF FFMPEG HAS SOX RESAMPLER BUILT IN, WILL BE TESTET THE FIRST TIME NEEDED var curDuration = 0.; // DURATION OF THE CURRENT FILE FOR PROGRESS // ============================================================================= // SET DEFAULT VALUES function init(){ // LOCATION OF THE SETTINGS FILE settingsFile = folders.content+gvar.pss+'default.txt'; // SETTINGS settings = { "LUFS" : -23., // Target Loudness "tolerance" : true, // Tolerance (Permitted Deviation) "tolerance_val" : .5, "Dial" : false, // Use Dialog Intelligence // VALIDATE "TruePeak" : true, // Max True Peak Level "TruePeak_val" : -1., "tplimit" : true, // Enable True Peak Limiter "Max_S" : false, // Max Short Term "Max_S_val" : 0, "Max_M" : false, // Max Momentary "Max_M_val" : 0, "LRA" : false, // Loudness Range "LRA_val" : 0, // EXPORT "export" : 'ask', // ask = DEFAULT, never, always "overwrite" : false, // overwrite existing files "files" : 'fixed', // fixed (Default), Only files that need fixing will be processsed // all = Files that don't need fixing wil be copied (unprocessed) "customFolder" : false, // Move fixed files to a custom folder "customFolder_path" : folders.home, // By default this is the users home folder "nametag" : true, // Tag added to fixed file "nametag_val" : '-R128', "subFolder" : false, // Move fixed files to a subfolder "subFolder_val" : 'R128', "validate" : true, // Re-analyse after processing, // slow but more reliable }; // ALLOWED FILE TYPES fileTypes = [ 'wav', 'aif', 'aiff', ]; // CHECK IF FFMPEG & FFPROBE ARE AVAILABLE FOR TRUE PEAK LIMITING has_ffmpeg = !checkDependencies("ffprobe,ffmpeg"); // CHECK IF FFMPEG HAS SOX RESAMPLER BUILT IN // WILL BE TESTET THE FIRST TIME NEEDED has_sox = "NOTDEFINED"; } // ============================================================================= // MAIN - AudLoud 0.7.2R02 // ============================================================================ function main() { // ----------------------------- // START THE MACHINE setProgress(0); // ----------------------------- // DEFAULT SETTINGS init(); // ----------------------------- // CUSTOM SETTINGS loadSettings(); // ----------------------------- // GET ALL FILES FROM THE APP var files = getFiles(); for (i=0;i x !== i && file.outFile === files[i].outFile)) { Object.assign(files[i], { file_icon: "ExclamationCircle", file_icon_color: "FFff0000", file_status: "The output filename is not unique!", fixme: -1 }); setFiles(files); continue; } // ------------------------------------ // FILE EXISTS BUT REMOVE if(fileExists(files[i]["outFile"]) && settings.overwrite){ deleteFile(files[i]["outFile"]); } // ------------------------------------ // FILE STILL EXISTS if (fileExists(files[i].outFile)) { Object.assign(files[i], { file_icon: "ExclamationCircle", file_icon_color: "FFff0000", file_status: "The output file exists!", fixme: -1 }); setFiles(files); continue; } // ------------------------------------ // COULD NOT READ THE FILE if (!infile) { Object.assign(files[i], { file_icon: "CheckSquare", file_icon_color: "FFff0000", file_status: "Could not analyse the file", fixme: -1 }); setFiles(files); continue; } // ------------------------------------ // VALID FILE - NO NEED TO FIX if ( infile.status == 1 ) { files[i]["file_icon"] = "CheckSquare"; files[i]["file_icon_color"] = "FF008800"; files[i]["file_status"] = "Passed"; files[i]["fixme"] = 0; } // NOT VALID FILE - NEED TO FIX else { files[i]["file_icon"] = "CheckSquare"; files[i]["file_icon_color"] = "FFff0000"; files[i]["file_status"] = "Failed"; files[i]["LUFS"] = Math.round(infile.levels.LUFS * 100) / 100; files[i]["TruePeak"] = Math.round(infile.levels.TruePeak * 100) / 100; files[i]["process_adjust"] = infile.levels.AdjustLevel_NoLimit; files[i]["process_limit"] = (infile.levels.TruePeak + infile.levels.AdjustLevel > (settings.TruePeak_val - 0.1) ? true : false); files[i]["fixme"] = 1; if(gvar.demo) msg.push('\n\n\n--------\nBuy Magnetron.APP to fix files'); needsfixing++; } // ------------------------------------ // TOOLTIP files[i]["file_tooltip"] = msg.join('\n'); // ------------------------------------ // UPDATE THE FILE LIST setFiles(files); } // ============================================================================= // IF SETTINGS IS AKS THEN ASK var doProcess = false; // ALWAYS EXPORT if( !gvar.demo && (settings.export == "yes" )){ doProcess = true; } // ASK TO EXPORT else if( !gvar.demo && settings.export == "ask" && needsfixing > 0){ if (askDialog().returns == 1) doProcess = true; } // ============================================================================= // PROCESS if( doProcess ) { // GET ALL FILES var files = getFiles(); // LOOP FILES THAT NEEDS FIXING for (i=0;i 0 ) { files[i]["file_icon"] = "ExclamationCircle"; files[i]["file_icon_color"] = "FFff0000"; files[i]["file_status"] = "Could not be fixed"; // REMOVE FILE IF ERRORS STILL EXIST if(fileExists(f.outFile)) deleteFile(f.outFile); } // FIXED else { files[i]["file_icon"] = "CheckSquare"; files[i]["file_icon_color"] = "FF0000FF"; files[i]["file_status"] = "Fixed"; } } // TOOLTIP files[i]["file_tooltip"] = files[i]["file_tooltip"]+msg.join('\n'); // ---- setFiles(files); // end loop } // end process } // ALL DONE setProgress(0); setMainMessage(""); setFiles(files); // end main } // ============================================================================= // GET THE LOUDNESS SPECIFICATIONS AND CHECK THEM, // IF NOT VALID RETURN AN ARRAY WITH ERRORS function validateLoudness(file) { // RETURN VALUE var rtn = []; var err = 0; // ANALYSES AUDIO LEVELS var levels = levelFileAnalyse( file , { "AdjustTargetType": "LUFS", "AdjustTargetLevel": settings.LUFS, "DialogGate": settings.Dial, }); // NO RESULTS if (levels == null) return false; // NO LOUDNESS FOUND if ( levels.LUFS == undefined ) return false; // CHECK LUFS const tolerance = settings.tolerance ? settings.tolerance_val : 0.1; if (Math.abs(settings.LUFS - levels.LUFS) >= tolerance) { rtn.push(`x Loudness did not validate (${toFixed(levels.LUFS, 1)}LUFS)`) && err++; } else { rtn.push(`✓ Loudness (${toFixed(levels.LUFS, 1)}LUFS)`); } // CHECK TRUE PEAK if (settings.TruePeak) { const [peak, peakVal] = [toFixed(levels.TruePeak, 1), toFixed(settings.TruePeak_val, 1)]; rtn.push(levels.TruePeak > settings.TruePeak_val ? `x Peak Level exceeded ${peakVal}dBTP (${peak}dBTP)` && err++ : `✓ Peak Level (${peak}dBTP)`); } // CHECK MAX S if (settings.Max_S) { const [maxS, maxSVal] = [toFixed(levels.Max_S, 1), toFixed(settings.Max_S_val, 1)]; rtn.push(levels.Max_S > settings.Max_S_val ? `x MaxS exceeded ${maxSVal} (${maxS}LU)` && err++ : `✓ MaxS (${maxS}LU)`); } // CHECK MAX M if (settings.Max_M) { const [maxM, maxMVal] = [toFixed(levels.Max_M, 1), toFixed(settings.Max_M_val, 1)]; rtn.push(levels.Max_M > settings.Max_M_val ? `x MaxM exceeded ${maxMVal} (${maxM}LU)` && err++ : `✓ MaxM (${maxM}LU)`); } // CHECK LRA if (settings.LRA) { const [lra, lraVal] = [toFixed(levels.LRA, 1), toFixed(settings.LRA_val, 1)]; rtn.push(levels.LRA > settings.LRA_val ? `x LRA exceeded ${lraVal} (${lra}LU)` && err++ : `✓ LRA (${lra}LU)`); } // RETURN return { "status" : ( err > 0 ? 0 : 1 ), // if error = 0, passed = 1 "msg" : rtn, "levels" : levels }; } // ============================================================================= // LEVEL ANALYSE PROGRESS CALLBACK function LevelFileAnalyse_Progress(file, progress) { setProgress( progress * 100 ); } // ============================================================================= // LEVEL PROCESS PROGRESS CALLBACK function LevelFileProcess_Progress(file, progress) { setProgress( progress * 100 ); } // ============================================================================= // FFMPEG CALLBACK function cmd_callback(cmd_name, cmd_output) { // DUE TO A BUG IN THE FFMPEG CALLBACK THE MS IS SHOWN AS MICRO SECONDS, // NOT MILISECCONDS. SO WE NEED TO DIVIDE BY 1000000 TO GET THE CORRECT TIME if (cmd_name === "ffmpeg" && cmd_output.length > 0 && curDuration > 0) { const progress = Math.round((parseInt(cmd_output.match(/out_time_us=(\d+)/)?.pop() || 0, 10) / 1000000) / curDuration * 100); setProgress(progress); } return { "terminate": isCanceled(), // use this option to end the process if there is no better alternative "input": "" // send console input, like a quit signal (followed by a 'return' 0x0D) }; } // ============================================================================= // ON CONFIG, SHOW DIALOG AND SAVE SETTINGS function onConfig() { if(gvar.demo){ } else{ // ----------------------------- // DEFAULT SETTINGS init(); // ----------------------------- // LOAD CUSTOM SETTINGS loadSettings(); // ----------------------------- // IF THE CUSTOM PATH IS INVALID, SET IT TO HOME FOLDER if(!fileExists(settings.customFolder_path)){ settings.customFolder_path = folders.home; } // ----------------------------- // SETTINGS DIALOG var r = showSettings(); // ----------------------------- // IF RESET IS PUSHED if(r.returns === -1) { deleteFile(settingsFile); // DELETE THE SETTINGS FILE init(); // RESET TO DEFAULT SETTINGS onConfig(); // OPEN THIS CONFIG FUNCTION AGAIN } // ----------------------------- // IF SAVED WAS PUSHED else if(r.returns === 1) { // CHECK THAT SUBFOLDER DOES NOT CONTAIN SLASHES settings.subFolder_val = settings.subFolder_val.replace(/\//g, ''); writeFile( settingsFile, JSON.stringify(settings) ); } // ----------------------------- } } // ============================================================================= // ON ABOUT, SHOW POPUP WITH BUTTON TO OPEN WEBSITE function onAbout() { var args = { "info": { "type": "texteditor", "bounds" : { "y" : 70, "w" : 400, "h" : 150 }, "visible": true, "multiline" : true, "wordwrap" : true, "default" : header.description+'\n\nMore information on the recipes page on our website.', "enabled" : false }, "showpage" : { "type" : "button", "label" : "show page", "returns" : 1 }, "close" : { "type" : "button", "label" : "close", "returns" : 0 } }; var r = dialog(header.title, "" , "", args); if (r.showpage == 1) launchInBrowser('https://magnetron.app/recipes/uuid/'+header.uuid+'/'); } // ============================================================================= // LOAD DEFAULT VALUES function loadSettings() { if (fileExists(settingsFile)) { Object.assign(settings, JSON.parse(readFile(settingsFile))); } return true; } // ============================================================================= function showSettings() { var form = { // -------------------------------- "spacer0": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": 20 }, }, // -------------------------------- "heading1" : { "type" : "text", "label" : "// TARGET - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", "just" : "l", "bounds" : { "x": 0, "w" : 600, "h" : 40 }, }, // -------------------------------- "LUFS_label" : { "type" : "text", "label" : "Loudnes", "bounds" : { "x": 0, "w" : 160, }, }, "LUFS_label2" : { "type" : "text", "label" : "0LU = ", "enabled" : false, "bounds" : { "y" : -1, "x": 0, "x" : 110, "w" : 80, }, }, "LUFS": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": -99, "max" : 0, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.LUFS }, "LUFS_d" : { "type" : "text", "label" : "LUFS", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- "tolerance_label" : { "type" : "text", "label" : "Tolerance", "bounds" : { "x": 0, "w" : 160, }, }, "tolerance": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.tolerance }, "tolerance_val": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": 0, "max" : 10, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.tolerance_val }, "tolerance_d" : { "type" : "text", "label" : "+/- LU", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- "dial_label" : { "type" : "text", "label" : "Dialogue Gate", "bounds" : { "x": 0, "w" : 160, }, }, "dial": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.dial }, // -------------------------------- "spacer0a": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": 20 }, }, // -------------------------------- "heading3" : { "type" : "text", "label" : "// VALIDATE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", "just" : "l", "bounds" : { "x": 0, "w" : 600, "h" : 40 }, }, // -------------------------------- "TruePeak_label" : { "type" : "text", "label" : "TruePeak", "bounds" : { "x": 0, "w" : 160, }, }, "TruePeak": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.TruePeak }, "TruePeak_val": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": -9, "max" : 0, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.TruePeak_val }, "TruePeak_d" : { "type" : "text", "label" : "dBFS", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- // TP Limiter "tplimit_label" : { "type" : "text", "label" : "Limit", "just" : "l", "bounds" : { "y": -1, "x": 450, "w" : 100, }, }, "tplimit": { "type": "togglebox", "bounds" : { "y": -1, "x": 560, "w" : 25, }, "label" : "", "toggle" : settings.tplimit }, // -------------------------------- "Max_S_label" : { "type" : "text", "label" : "Max-S", "bounds" : { "x": 0, "w" : 160, }, }, "Max_S": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.Max_S }, "Max_S_val": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": 0, "max" : 12, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.Max_S_val }, "Max_S_d" : { "type" : "text", "label" : "LU", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- "Max_M_label" : { "type" : "text", "label" : "Max-M", "bounds" : { "x": 0, "w" : 160, }, }, "Max_M": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.Max_M }, "Max_M_val": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": 0, "max" : 20, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.Max_M_val }, "Max_M_d" : { "type" : "text", "label" : "LU", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- "LRA_label" : { "type" : "text", "label" : "LRA", "bounds" : { "x": 0, "w" : 160, }, }, "LRA": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.LRA }, "LRA_val": { "type": "slider", "style": "slider", "visible": true, "range" : { "min": 0, "max" : 20, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x": 180, "w" : 100, }, "value": settings.LRA_val }, "LRA_d" : { "type" : "text", "label" : "LU", "bounds" : { "y" : -1,"x": 290, "w" : 160, }, }, // -------------------------------- "spacer1": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": 20 }, }, // -------------------------------- "heading2" : { "type" : "text", "label" : "// EXPORT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", "just" : "l", "bounds" : { "x": 0, "w" : 600, "h" : 40 }, }, // -------------------------------- // Fix "export_label" : { "type" : "text", "label" : "Export", "just" : "l", "bounds" : { "x": 0, "w" : 100, }, }, "export" : { "type" : "combobox", "default" : settings.export, "items" : "ask,yes,no", "bounds" : { "y" : -1, "x": 180, "w" : 120, }, "returns" : 0, }, // -------------------------------- // Overwrite "overwrite_label" : { "type" : "text", "label" : "Overwrite", "just" : "l", "bounds" : { "y": -1, "x": 450, "w" : 100, }, }, "overwrite": { "type": "togglebox", "bounds" : { "y": -1, "x": 560, "w" : 25, }, "label" : "", "toggle" : settings.overwrite }, // -------------------------------- "files_label" : { "type" : "text", "label" : "Files", "just" : "l", "bounds" : { "x": 0, "w" : 100, }, }, "files" : { "type" : "combobox", "default" : settings.files, "items" : "all,fixed", "bounds" : { "y" : -1, "x": 180, "w" : 120, }, "returns" : 0, }, // -------------------------------- "customFolder_label" : { "type" : "text", "label" : "Custom folder", "just" : "l", "bounds" : { "x": 0, "w" : 160, }, }, "customFolder": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.customFolder }, "customFolder_path" : { "type" : "fileselect", "path" : settings.customFolder_path, "editable" : false, "dir" : true, "saving" : true, "label" : "empty", "bounds" : { "y": -1, "x": 180, "w" : 400, }, "visible": false, }, // -------------------------------- "subFolder_label" : { "type" : "text", "label" : "Sub folder", "just" : "l", "bounds" : { "x": 0, "w" : 160, }, }, "subFolder": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.subFolder }, "subFolder_val": { "type": "texteditor", "bounds" : { "y": -1, "x" : 180, "w" : 400, }, "multiline" : false, "wordwrap" : false, "default" : settings.subFolder_val, "visible": true, }, // -------------------------------- "nametag_val_label" : { "type" : "text", "label" : "Name tag", "just" : "l", "bounds" : { "x": 0, "w" : 160, }, }, "nametag": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.nametag }, "nametag_val": { "type": "texteditor", "bounds" : { "y": -1, "x" : 180, "w" : 400, }, "multiline" : false, "wordwrap" : false, "default" : settings.nametag_val, "visible": false, }, // -------------------------------- "validate_label" : { "type" : "text", "label" : "Revalidate", "bounds" : { "x": 0, "w" : 160, }, }, "validate": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.validate }, // -------------------------------- "spacer3" : { "type" : "text", "label" : " ", "just" : "r", "bounds" : { "x": 0, "w" : 200, "h" : 20 }, }, // -------------------------------- "reset" : { "type" : "button", "label" : "reset", "bounds" : { "x": 330, "w" : 80 }, "returns" : -1 }, "cancel" : { "type" : "button", "label" : "cancel", "bounds" : { "y": -1, "x": 415, "w" : 80 }, "returns" : 0 }, "save" : { "type" : "button", "label" : "save", "bounds" : { "y": -1, "x": 500, "w" : 80 }, "returns" : 1 }, // -------------------------------- }; // -------------------------------- return dialog(header.title, "", "w", form); } // ============================================================================ // DIALOG CALLBACK function dialog_callback(props) { const directMapping = { export: "text", files: "text", overwrite: "toggle", customFolder: "toggle", customFolder_path: "path", nametag: "toggle", nametag_val: "text", subFolder: "toggle", subFolder_val: "text", LUFS: "value", TruePeak: "toggle", TruePeak_val: "value", tplimit: "toggle", tolerance: "toggle", tolerance_val: "value", Max_S: "toggle", Max_S_val: "value", Max_M: "toggle", Max_M_val: "value", LRA: "toggle", LRA_val: "value", dial: "toggle", validate: "toggle" }; // Dynamische toekenning if (directMapping[props.name]) { settings[props.name] = props[directMapping[props.name]]; } // ---------------- // SHOW/HIDE WHEN EXPORT var exp = ( settings['export'] == 'yes' || settings['export'] == 'ask' ? true : false ); // ---------------- // UPDATE DIALOG dialog({ "TruePeak_val" : { "visible" : settings['TruePeak'] }, "TruePeak_d" : { "visible" : settings['TruePeak'] }, "tplimit_label" : { "visible" : settings['TruePeak'] }, "tplimit" : { "visible" : settings['TruePeak'] }, "tolerance_val" : { "visible" : settings['tolerance'] }, "tolerance_d" : { "visible" : settings['tolerance'] }, "Max_S_val" : { "visible" : settings['Max_S'] }, "Max_S_d" : { "visible" : settings['Max_S'] }, "LRA_val" : { "visible" : settings['LRA'] }, "LRA_d" : { "visible" : settings['LRA'] }, "Max_M_val" : { "visible" : settings['Max_M'] }, "Max_M_d" : { "visible" : settings['Max_M'] }, "files_label" : { "enabled" : exp }, "files" : { "enabled" : exp, "visible" : exp, "default" : settings['files'] }, "overwrite_label" : { "enabled" : exp, "visible" : exp }, "overwrite" : { "enabled" : exp, "visible" : exp }, "validate_label" : { "enabled" : exp }, "validate" : { "enabled" : exp, "visible" : exp }, "customFolder_label" : { "enabled" : exp, }, "customFolder" : { "enabled" : exp, "visible" : exp }, "customFolder_path" : { "enabled" : exp, "visible" : exp && settings['customFolder'] }, "subFolder_label" : { "enabled" : exp, }, "subFolder" : { "enabled" : exp, "visible" : exp }, "subFolder_val" : { "enabled" : exp, "visible" : exp && settings['subFolder'] }, "nametag_val_label" : { "enabled" : exp, }, "nametag" : { "enabled" : exp, "visible" : exp }, "nametag_val" : { "enabled" : exp, "visible" : exp && settings['nametag'] }, }); // ---------------- } // ============================================================================= // DIALOG THE ASK THE USER TO FIX OR SKIP function askDialog() { var form = { // -------------------------------- "spacer0": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": 20 }, }, // -------------------------------- "text" : { "type" : "text", "label" : "Some files did not validate.\nDo you want to try to fix them?", "bounds" : { "x": 0, "w" : 550, "h" : 100 }, }, // -------------------------------- "text2" : { "type" : "text", "label" : "", "bounds" : { "x": 0, "w" : 550, "h" : 150 }, }, "skip" : { "type" : "button", "label" : "skip", "bounds" : { "x": 340, "w" : 100, }, "returns" : 0 }, "fix" : { "type" : "button", "label" : "fix", "bounds" : { "y": -1, "x": 450, "w" : 100 }, "returns" : 1 }, // -------------------------------- "text3" : { "type" : "text", "label" : "", "bounds" : { "x": 0, "w" : 550, "h" : 250 }, }, }; return dialog(header.title, '', "w", form); } // ============================================================================= function processFile(f) { // ---------------------------------------- var msg = []; var err = 0; // ---------------------------------------- // PROCESS USING FFMPEG if(f.process_limit && has_ffmpeg) { // WE NEED THE SAMPLE RATE BECAUSE IT WILL UPSAMPLE THE FILE TO 192 FOR THE TRUE PEAK // FILTER AND WE NEED TO BRING IT BACK TO THE ORIGINAL SAMPLE RATE var ffprobe = cmd("ffprobe", [ '-v', 'error', '-select_streams', 'a:0', '-show_entries', 'format=size:stream=sample_rate,bits_per_sample,channels:format=duration', '-of', 'default=noprint_wrappers=1', '-i', f.path ]); const sample_rate = parseInt(ffprobe.match(/(?:sample_rate=)(.*?)(?=[\n\r])/m)?.[1] || -1); const channels = parseInt(ffprobe.match(/(?:channels=)(.*?)(?=[\n\r])/m)?.[1] || -1); // SET THE DURATION FOR THE PROGRESS BAR curDuration = parseFloat(ffprobe.match(/(?:duration=)(.*?)(?=[\n\r])/i)?.[1] || 1); // LOUDNORM FILTER const tpmax = Math.max( Math.round((settings.TruePeak_val - 0.01) * 1000) / 1000, -9.0 ) const tpmeasured = Math.round((f.TruePeak - 0.01) * 100) / 100; const tplimit = (settings.tplimit ? `:TP=` + tpmax + `:measured_TP=` + tpmeasured : ''); const loudnormFilter = `loudnorm=linear=true` + `:I=${Math.round(settings.LUFS * 100) / 100}` + `:measured_I=${Math.round(f.LUFS * 100) / 100}` + tplimit + `:print_format=summary`; // DETECT SOX SUPPORT IN FFMPEG if (has_ffmpeg && has_sox === "NOTDEFINED") { const ffmpegVersion = cmd("ffmpeg", ["-version"]); // SAVE FOR NOW AND GLOBAL FOR LATER has_sox = ffmpegVersion.includes("--enable-libsoxr"); } // RESAMPLING const resampling = has_sox ? `aresample=resampler=soxr:out_sample_rate=${sample_rate}:precision=28` : ['-ar', sample_rate]; // BUILD FFMPEG COMMAND const ffmpegArgs = [ '-i', f.path, '-filter:a', loudnormFilter, ...(Array.isArray(resampling) ? resampling : [resampling]), '-ac', channels, '-loglevel', 'error', '-progress', 'pipe:1', '-nostats', f.outFile ]; // RUN THE FFMPEG COMMAND cmd("ffmpeg", ffmpegArgs); } // ---------------------------------------- // PROCESS USING BUILT IN PROCESSOR else { msg.push('- processed using built-in processor'); // adjust properties var args = { "AdjustLevel_NoLimit" : f.process_adjust, }; // PROCESS var outfile = levelFileProcess( f.path, f.outFile, args ); // CHECK IF THE FILE WAS PROCESSED if (!outfile || typeof outfile.error !== 'undefined') { err++; msg.push(outfile.error); } } // ---------------------------------------- return { "msg" : msg, "err" : err }; }