header={ "chef": "BeatRig", "dependencies": "", "title": "Podcast & Streaming Loudness - AES77", "description": "Validate or fix a file to match the AES77 and TD1008 standard for online streaming and podcasts", "instructions": "Drop file(s) here", "recipe_version": "1.433", "tags": "audio, loudness, ATSC A/85, online streaming", "type": "demo", "os": "windows,macOS", "palette": "Clean Slate", "time": 1698840108, "core_version": "0.6.1", "magnetron_version": "1.0.275", "functions": "main,onAbout", "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=", "flavour": "yeAtx0lEWFoniCmZJ/txlm7ojLcGVxwIQstglHlgmYzDSiZSHkVzcuw2AEtf7/txyKFiLElJ2CMkwMJG+34c0A8ROGwwWx1QwJI8YaBCVAZC63UZpMx5kK+dY4pFQswzcaXRO0rKsS6miwlCSm1lgzijZ2tNd+M/UFKC8PA2nyk=", "uuid": "62649fd95a084932a59bf5455b9f53ef" }; // ============================================================================= var settings = { "LUFS" : -18., // Target Loudness "TruePeak" : -1., // Max True Peak Level "Max_S" : false, // Max Short Term "Max_M" : false, // Max Momentary "LRA" : false, // Max Loudness Range "Dial" : true, // Use Dialog Intelligence "tolerance" : 0.5, // Tolerance (Permitted Deviation) "tag" : '-AES77', // Tag added to corrected file "aboutLink" : 'https://magnetron.app/recipes/podcast-loudness/', }; // ============================================================================= var fileTypes = [ 'wav', 'aif', 'aiff', ]; var surroundOrder = "SMTPE_ITU_AC3"; /* SMTPE_ITU_AC3, // L-R-C-Lfe-Ls-Rs FilmDolbyDigital, // L-C-R-Ls-Rs-Lfe DTS_ProControl // L-R-Ls-Rs-C-Lfe */ // ============================================================================= // MAIN // ============================================================================ function main() { // ----------------------------- // START THE MACHINE setProgress(0); // ----------------------------- // GET ALL FILES FROM THE APP var files = getFiles(); for (i=0;i maxPeak ? true : false); if(gvar.demo) msg.push('\n\n\n--------\nBuy Magnetron.APP to fix files'); needsfixing.push(i); } // ------------------------------------ // TOOLTIP files[i]["file_tooltip"] = msg.join('\n'); // ------------------------------------ // UPDATE THE FILE LIST setFiles(files); } // ------------------------------------------------------------------------- // PROCESS // ------------------------------------------------------------------------- if(needsfixing.length > 0 && !gvar.demo) { // get the location of first file to set the custom path var exportfolder = getPathInfo(files[needsfixing[0]].path); exportfolder = exportfolder.folder; // show dialog var r = showDialog(exportfolder); // FIXING, YES PLEASE if (r.fix == 1) { // GET ALL FILES var files = getFiles(); // LOOP FILES THAT NEEDS FIXING for (i=0;i 0 ) { files[fileid]["file_icon"] = "ExclamationCircle"; files[fileid]["file_icon_color"] = "FFff0000"; files[fileid]["file_status"] = "Could not be fixed"; } // FIXED else { files[fileid]["file_icon"] = "CheckSquare"; files[fileid]["file_icon_color"] = "FFFF8800"; files[fileid]["file_status"] = "Fixed"; msg.push("adjusted "+toFixed(thisfile.process_adjust, 1)+"dB"); } // EXTRA LIMITER WARNING if(settings.TruePeak == true) { msg.push("The limiter was turned on to prevent clipping"); } // REMOVE IF ERRORS if(err> 0 && fileExists(exportfile)) deleteFile(exportfile); // TOOLTIP files[fileid]["file_tooltip"] = msg.join('\n'); // ---- setFiles(files); } } } // ALL DONE setProgress(0); setMainMessage(""); setFiles(files); } // ============================================================================= // GET THE LOUDNESS SPECIFICATIONS AND CHECK THEM, // IF NOT VALID RETURN AN ARRAY WITH ERRORS function validateLoudness(file, settings ){ // --------------------------------------------------------------------- // RETURN VALUE var rtn = []; var err = 0; // AUDIO LEVELS var levels = false; // --------------------------------------------------------------------- // CREATE A TEMPORARY FILE IF NEEDED var temp_file = false; var path = getPathInfo(files[i].path); var ext = toLowerCase(path.ext); // --------------------------------------------------------------------- // ANALYSES AUDIO LEVELS var levels = levelFileAnalyse( file , { "AdjustTargetType": "LUFS", "AdjustTargetLevel": settings.LUFS, "DialogGate": settings.Dial, "CalibrationLU": settings.LUFS, "AdjustLevelLowered": true, "LimitThresholddB" : levels.TruePeak, "SurroundOrder" : surroundOrder, }); // --------------------------------------------------------------------- // PARSE RESULTS // ERROR if (levels == null) return false; if ( levels.LUFS == undefined ) { return false; } // FILE DID GET RESULTS, CHECK THEM if ( Math.abs(settings.LUFS - levels.LUFS) > settings.tolerance ) { err++; rtn.push("Loudness did not validate ("+toFixed(levels.LUFS, 1)+"LUFS)"); } if ( levels.TruePeak > settings.TruePeak ){ err++; rtn.push("Peak Level exceeded "+toFixed(settings.TruePeak, 1)+" ("+toFixed(levels.TruePeak, 1)+"dBTP)"); } if ( settings.Max_S != false && levels.Max_S > settings.Max_S ) { err++; rtn.push("MaxS exceeded "+toFixed(settings.Max_S, 1)+" ("+toFixed(levels.Max_S, 1)+"LUFS)"); } if ( settings.Max_M != false && levels.Max_M > settings.Max_M ) { err++; rtn.push("MaxM exceeded "+toFixed(settings.Max_M, 1)+" ("+toFixed(levels.Max_M, 1)+"LUFS)"); } if ( settings.LRA != false && levels.LRA > settings.LRA ) { err++; rtn.push("LRA exceeded "+toFixed(settings.LRA, 1)+" ("+toFixed(levels.LRA, 1)+"LUFS)"); } // if error = 0, passed = 1 return { "status" : ( err > 0 ? 0 : 1 ), "msg" : rtn, "levels" : levels }; // --------------------------------------------------------------------- } // ============================================================================= function showDialog(exportfolder) { var form = { "intro" : { "type" : "text", "label" : "Some files did not validate.\nDo you want to try to fix them?", "just" : "l", "bounds" : { "x": 20, "w" : 550, "h" : 100}, }, "tag_label" : { "type" : "text", "label" : "append to filename", "just" : "l", "bounds" : { "x": 20, "w" : 200, }, }, "tag": { "type": "texteditor", "bounds" : { "y" : -1, "x" : 320, "w" : 100, }, "multiline" : false, "wordwrap" : false, "default" : settings.tag }, "path_label" : { "type" : "text", "label" : "use source folder", "just" : "l", "bounds" : { "x": 20, "w" : 160, }, }, "path_toggle": { "type": "togglebox", "bounds" : { "y": -1, "x": 320, "w" : 25, }, "label" : "", "toggle" : true }, "custompath_label" : { "type" : "text", "label" : "folder", "just" : "l", "bounds" : { "x": 20, "w" : 160, }, "visible": false, }, "custompath" : { "type" : "fileselect", "path" : exportfolder, "editable" : false, "dir" : true, "saving" : true, "label" : "empty", "bounds" : { "y": -1, "x": 180, "w" : 340, }, "visible": false, }, "spacer1" : { "type" : "text", "label" : " ", "just" : "r", "bounds" : { "x": 120, "w" : 200, "h" : 100 }, }, "fix" : { "type" : "button", "label" : "fix", "is_okay" : true, "bounds" : { "x": 310, "w" : 100 }, "returns" : 1 }, "skip" : { "type" : "button", "label" : "skip", "is_cancel" : true, "bounds" : { "y": -1, "x": 420, "w" : 100, }, "returns" : 0 } }; //return dialog(header.title, text, "w", form); return dialog(form); } // ============================================================================= // DIALOG CALLBACK function dialog_callback(props) { if (props["name"] == "path_toggle") var r = dialog({ "custompath" : { "visible" : !props["toggle"] }, "custompath_label" : { "visible" : !props["toggle"] } }); } // ============================================================================= // 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() { setProgress(101); } // ============================================================================= // 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(settings.aboutLink); }