header={ "recipe_version": "1.1872", "title": "Custom Audio Normalisation", "description": "Analyse and fix audio levels with custom settings", "tags": "default", "chef": "BeatRig", "instructions": "", "type": "default", "os": "windows,macOS", "palette": "Clean Slate", "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=", "flavour": "F8lLxVtgmxhB2AvB6H13hA/DirVGjMwvwqnYbRRwyB+CmurVTnPLqX5WN6Uzuv1EoOSYhVtXMJwSpCyyrdFJg3jrthhFFBQZ9lwPonXS13oDSTx1y6Ppenb2cBDxZktRzoAT4Nb5qlrLVxBqcJOaP0gKslA4wKCQDvaRbpZbCww=", "time": 1737580631, "core_version": "0.7.4", "magnetron_version": "1.0.341", "functions": "main,onAbout", "uuid": "d1cd1324d3bb4a09be75f76d5971e657", "dependencies": "ffprobe,ffmpeg" }; // ============================================================================ // GLOBAL FILES VAR var files = []; var curDuration = 0.; // ============================================================================ // DEFAULTS var settings = { // "AdjustTargetType" : "Loudness", "AdjustTargetLevel" : -23.0, "AdjustTargetLevel_scale" : 'LUFS', // loudness settings "relativeGate" : true, "relativeGate_level" : -10.0, "dialogGate" : false, // limiter "peakLimit" : true, "peakLimit_type" : "TruePeak", "peakLimit_level" : -1.0, "lraLimit" : false, "lraLimit_level" : 18.0, // export path & name "export" : true, "exportMode" : "always", "exportRef_select" : "individual", "exportRef_reffile" : "", "tolerance" : true, "tolerance_val" : 0.5, "customFolder" : false, "customFolder_path" : "", "subFolder" : true, "subFolder_val" : "export", "nametag" : false, "nametag_val" : "-R128", //validate "validate" : true, }; // ============================================================================ // ALLOWED FILE TYPES var fileTypes = [ 'wav', 'aif', 'aiff', ]; // ============================================================================ function main() { // ----------------------------- // START THE MACHINE _e("TITLE: " + header.title); _e("VERSION: " + header.recipe_version +" // "+ header.time ); setProgress(0); // ----------------------------- // GET ALL FILES FROM THE APP files = getFiles(); // CHECK IF THERE ARE FILES FOR THE DIALOG var fileCount = 0; // a list containing all valid files for the reference combobox var fileList = []; // CHECK IF FFMPEG & FFPROBE ARE AVAILABLE FOR TRUE PEAK LIMITING settings = loadSettings(settings); // CHECK IF FFMPEG & FFPROBE ARE AVAILABLE FOR TRUE PEAK LIMITING settings.ffmpeg = !checkDependencies("ffprobe,ffmpeg"); // CHECK IF FFMPEG HAS SOX RESAMPLER BUILT IN // WILL BE TESTET THE FIRST TIME NEEDED settings.sox = "NOTDEFINED"; // ========================================================================= // LOOP ALL FILES files.forEach(item => { // NO ERROR var err = false; // START WITH EMPTY TOOLTIP ARRAY item.ttip = []; // CHECK IF FILE CAN BE CHECKED var path = getPathInfo(item.path); // CHECK IF FILE CAN BE READ if(!path.isfile){ err = "The file could not be read"; } // CHECK IF FILE IS RIGHT EXTENSION else if(fileTypes.indexOf( toLowerCase(path.ext) ) == -1){ err = "The file can not be checked."; item.ttip.push("Please add a WAV or AIFF file"); } // FILE IS OK else { fileCount++; } item.file_icon = err ? "ExclamationCircle" : "HourglassO"; item.file_icon_color = err ? "FFFF0000" : "FFdddddd"; item.file_status = err ? err : " "; item.dialogmissing = false; // if dialog is required but not found item.skip = err ? 1 : 0; // skip if error item.err = err ? 1 : 0; item.wrn = err ? 1 : 0; item.ttip = []; _set(item); }); // ========================================================================= // IF NO FILES, WARN AND STOP if( fileCount == 0){ abort("Add some files first!"); } // ======================================================================== // DIALOG settings.customFolder_path = (settings.customFolder_path == "" ? folders.home : settings.customFolder_path ); settings.exportRef_reffile = fileList.join(','); // PREPARE DIALOG FIELDS var fields = dialogJSON(settings); var r = dialog(fields); if(r.cancel) { return; }; // REPLACE VALUES ONE-BY-ONE for (var j = 0; j < getObjectSize(r); j++) { settings[getObjectKey(r, j)] = getObjectValue(r, j); } // STORE SETTINGS FOR THE NEXT TIME storeSettings(settings); // ======================================================================== // ANALYSE // ======================================================================== var maxLUFS = -99.; var maxPEAK = -99.; var maxTP = -99.; var maxPPM = -99.; files.forEach((item, index) => { // ------------------------------------ // SHORTHAND var path = getPathInfo(item.path); // ------------------------------------ // CHECK IF USER CANCELLED hasUserCanceled(); if( item.skip == 1 ) { return; } // ------------------------------------ // STATUS var err = 0; // COUNT ERRORS // ------------------------------------ // SET UI MESSAGE & CURRENT FILE ICON setMainMessage("analysing "+(index+1)+"/"+fileCount); item.file_icon = "CaretSquareORight"; item.file_icon_color = "FF777777"; item.file_status = "analysing..."; _set(item); // ------------------------------------ // ANALYSES AUDIO LEVELS var levels = levelFileAnalyse( item.path , { "AdjustTargetType": settings.AdjustTargetLevel, "AdjustTargetLevel": settings.AdjustTargetLevel, "dialogGate": settings.Dial, "CalibrationLU": settings.LUFS, "CalibrationPPM" : -9., "AdjustLevelLowered": false, }); // levels item.LUFS = levels.LUFS; item.Peak = levels.Peak; item.TruePeak = levels.TruePeak; item.PPM_Max = levels.PPM_Max; item.LRA = levels.LRA; // adjust level if(settings.AdjustTargetType == 'Loudness') { item.adjust = settings.AdjustTargetLevel - levels.LUFS; } else if(settings.AdjustTargetType == 'TruePeak') { item.adjust = settings.AdjustTargetLevel - levels.TruePeak; } else if(settings.AdjustTargetType == 'Peak') { item.adjust = settings.AdjustTargetLevel - levels.Peak; } else if(settings.AdjustTargetType == 'PPM') { item.adjust = settings.AdjustTargetLevel - levels.PPM_Max; } // ------------------------------------ // CHECK IF LIMITER SHOULD BE ENABLED item.enableLimiter = false; item.clipPrevent = false; if(settings.peakLimit == 1 ){ if( settings.peakLimit_type == 'TruePeak' && levels.TruePeak + item.adjust > settings.peakLimit_level ) { item.enableLimiter = true; } if( settings.peakLimit_type == 'Peak' && levels.Peak + item.adjust > settings.peakLimit_level ) { item.enableLimiter = true; } } else{ // PREVENT FROM CLIPPING WHEN SAMPLE PEAK EXCEEDS 0dB if(levels.Peak + item.adjust > 0.){ item.adjust = 0 - levels.Peak; item.clipPrevent = true; } } // ------------------------------------ // CHECK IF TOLERANCE IS ENABLED AND VALUE IS WITHIN TOLERANCE item.enableTolerance = false; if (settings.tolerance && Math.abs(item.adjust) < settings.tolerance_val) { item.enableTolerance = true; } // ------------------------------------ // KEEP TRACK OF MAX VALUES maxLUFS = (levels.LUFS > maxLUFS ? levels.LUFS : maxLUFS ); maxPEAK = (levels.Peak > maxPEAK ? levels.Peak : maxPEAK ); maxTP = (levels.TruePeak > maxTP ? levels.TruePeak : maxTP ); maxPPM = (levels.PPM_Max > maxPPM ? levels.PPM_Max : maxPPM ); // ------------------------------------ item.ttip.push('INPUT:'); item.ttip.push('- Loudness I : '+toFixed(item.LUFS, 1)+ 'LUFS'); item.ttip.push('- Sample Peak : '+toFixed(item.Peak, 1)+ 'dB'); item.ttip.push('- True Peak : '+toFixed(item.TruePeak, 1)+ 'dBTP'); item.ttip.push('- PPM : '+toFixed(item.PPM_Max, 1)+ 'dB'); // ------------------------------------ // SET OUTPUT PATH if(settings.customFolder == 1){ item.output_path = settings.customFolder_path; } else{ item.output_path = path.folder; } // output path should always have an traling slash item.output_path = ( item.output_path.charAt(item.output_path.length - 1) == gvar.pss ? item.output_path : item.output_path+gvar.pss ); // CHECK IF OUTPUT PATH IS VALID if( !fileExists(item.output_path) ){ item.skip = 1; err = "Could not export to this path"; } // ------------------------------------ // subFolder_val if(settings.subFolder == 1){ var p = settings.subFolder_val; var l = settings.subFolder_val.length; item.output_path = item.output_path +( p.charAt( 0) == gvar.pss ? p.substr(1, l - 1) : p ) +( p.charAt( l-1) == gvar.pss ? '' : gvar.pss ); } // ------------------------------------ // OUTPUT FILE NAME // ALSO INCLUDE nametag_val IF SET if(settings.nametag == 1){ item.output_name = path.basename+settings.nametag_val+'.'+path.ext; } else{ item.output_name = path.filename; } // ------------------------------------ item.output_file = item.output_path+item.output_name; // ------------------------------------ // CHECK IF SOURCE AND DEST FILE ARE THE SAME PATH if( item.path == item.output_file ){ if(settings.export){ err = 'Cannot overwrite source file'; } item.skip = 1; } // CHECK IF FILE ALLREADY EXISTS if( fileExists(item.output_file) ){ if(settings.export){ err = 'Could not export: file exists'; } item.skip = 1; } // ------------------------------------ // UPDATE THE FILE LIST item.file_icon = err ? "ExclamationCircle" : "HourglassO"; item.file_icon_color = err ? "FFFF0000" : "FFdddddd"; item.file_status = err ? err : " "; item.skip = err ? 1 : 0; _set(item); // ------------------------------------ }); // ======================================================================== // PROCESSING // ======================================================================== // GROUP MODE REFERENCE FILE // USE THE ADJUSTMENT FROM 1 SPECIFIC FILE FOR ALL if(settings.exportRef_select == 'reference'){ var id = parseInt(settings.exportRef_reffile.split(") ")[0]); var refAdjust = files[id].adjust; var refClipPrecent = files[id].clipPrevent; } if(settings.export){ files.forEach((item, index) => { // ------------------------------------ // CHECK IF USER CANCELLED hasUserCanceled(); // ------------------------------------ // SKIP FILES THAT ARE MARKED if( item.skip == 1 ) { return; } // ------------------------------------ // SET UI MESSAGE & CURRENT FILE ICON setMainMessage("processing "+(index+1)+"/"+files.length); setProgress(101); // ------------------------------------ // CHECK IF OUTPUT EXISTS OR CREATE if( !fileExists(item.output_path) ){ makeFolder(item.output_path); } // ------------------------------------ item.file_icon = "CaretSquareORight"; item.file_icon_color = "FF777777"; item.file_status = "processing..."; _set(item); // ------------------------------------ // GROUP MODES if(settings.exportRef_select == 'loudest') { item.adjust = maxAdjust; item.clipPrevent = maxClipPrevent; } else if(settings.exportRef_select == 'reference') { item.adjust = refAdjust; item.clipPrevent = refClipPrecent; } // ------------------------------------ // NO ADJUSTMENT if ( (item.adjust == 0. || item.enableTolerance) && item.enableLimiter == false ) { _e(item); // JUST COPY THE FILE if(settings.exportMode == 'always') { item.ttip.push('\nNO ADJUSTMENT:'); item.ttip.push('- file is copied to the new location'); copyFile( item.path, item.output_file ); } } // ------------------------------------ // ELSE >> PROCESS USING FFMPEG LOUDNORM FOR TRUE PEAK OR LRA LIMITING else if( settings.AdjustTargetType == 'Loudness' && settings.ffmpeg && (item.enableLimiter && settings.peakLimit_type == 'TruePeak') ) { item.ttip.push('\nADJUST:'); item.ttip.push('- processed using FFMPEG and limiting'); item.ttip.push('- Adjust : '+(item.adjust > 0 ? '+' : '')+toFixed(item.adjust, 1)+ 'dB'); // WE NEED THE SAMPLE RATE BECAUSE IT WILL UPSAMPLE THE FILE TO 192 FOR THE TRUE PEAK // FILTER AND WE NEE TO BRING IT BACK TO THE ORIGINAL SAMPLE RATE let ffprobe = cmd("ffprobe", [ '-v', 'error', '-select_streams', 'a:0', // '-show_entries', 'stream=sample_rate,channels', '-show_entries', 'format=size:stream=sample_rate,bits_per_sample,channels:format=duration', '-of', 'json', '-i', item.path ]); // Parse de JSON-output let ffprobeData = JSON.parse(ffprobe); // Extract de benodigde gegevens let sample_rate = parseInt(ffprobeData.streams?.[0]?.sample_rate || 0, 10); let channels = parseInt(ffprobeData.streams?.[0]?.channels || 0, 10); // SET THE DURATION FOR THE PROGRESS BAR curDuration = parseFloat(ffprobeData.format?.duration || 0); // PREPARE THE FFMPEG CALL let specs = [ 'linear=true', `I=${settings.AdjustTargetLevel}`, `measured_I=${(item.LUFS).toFixed(3)}`, ...(settings.peakLimit ? [ `TP=${(settings.peakLimit_level - 0.1).toFixed(3)}`, `measured_TP=${item.TruePeak.toFixed(3)}` ] : []), 'print_format=summary' ].join(':'); // TEST IF FFMPEG HAS SOX BUILT IN if(settings.ffmpeg && settings.sox == "NOTDEFINED"){ var ffmpeg = cmd("ffmpeg", ['-version']); // settings.sox = containsString(ffmpeg, '--enable-libsoxr') ? true : false; has_sox = ffmpeg.includes('--enable-libsoxr'); } // IF SOX RESAMPLER IS PRESENT if(settings.sox){ var ffmpeg = cmd("ffmpeg", [ '-i',item.path, '-filter:a','loudnorm='+specs+',aresample=resampler=soxr:out_sample_rate='+sample_rate+':precision=28', '-ac',channels, '-loglevel', 'error', '-progress', 'pipe:1', '-nostats', item.output_file, ]); } // NO SOX IS PRESENT, FALLBACK TO DEFAULT else{ var ffmpeg = cmd("ffmpeg", [ '-i',item.path, '-filter:a','loudnorm='+specs, '-ar',sample_rate, '-loglevel', 'error', '-progress', 'pipe:1', '-nostats', item.output_file, ]); } } // ------------------------------------ // ELSE >> PROCESSING USING BUILT IN PROCESSING WITH OR WITHOUT SAMPLE PEAK LIMITER else { item.ttip.push('\nADJUST:'); item.ttip.push('- processed using built-in processing:'); item.ttip.push('- Adjust : '+(item.adjust > 0 ? '+' : '')+toFixed(item.adjust, 1)+ 'dB'); var properties = { "AdjustLevel_NoLimit" : item.adjust, "NeedLimiting" : item.enableLimiter, "LimitThresholddB" : settings.peakLimit_level, }; levelFileProcess( item.path, item.output_file, properties ); } // ------------------------------------ if( item.clipPrevent ){ item.ttip.push('- COULD NOT ADJUST WITHOUT CLIPPING'); } if(!settings.sox) { item.ttip.push( '- Your FFMPEG does not contain the Sox Resampler'); } // ------------------------------------ // SET UI MESSAGE & CURRENT FILE ICON item.file_icon = "CheckSquare"; item.file_icon_color = "FFdddddd"; item.file_status = "processing done"; _set(item); }); } // ======================================================================== // ======================================================================== // VALIDATE - AND FINAL RESULTS // ======================================================================== // ======================================================================== if( settings.validate ) { files.forEach((item, index) => { // ------------------------------------ // NO VALIDATION if( item.skip == 1 ) { return; } // ------------------------------------ // CHECK IF USER CANCELLED hasUserCanceled(); // ------------------------------------ item.file_icon = "CaretSquareORight"; item.file_icon_color = "FF777777"; item.file_status = "validating..."; _set(item); // ------------------------------------ // SET UI MESSAGE & CURRENT FILE ICON setMainMessage("validating "+(index+1)+"/"+files.length); // FILE STATUS var err = false; // COUNT ERRORS var wrn = 0; // COUNT WARNINGS // ------------------------------------ // DO NOT VALIDATE, if(!settings.validate) { item.ttip.push('\nOUTPUT (APPROXIMATELY!):'); item.ttip.push('- Loudness I : '+toFixed(item.LUFS+item.adjust, 1)+ 'LUFS'); if(!settings.peakLimiter){ item.ttip.push('- Sample Peak : '+toFixed(item.Peak+item.adjust, 1)+ 'dB'); item.ttip.push('- True Peak : '+toFixed(item.TruePeak+item.adjust, 1)+ 'dBTP'); item.ttip.push('- PPM : '+toFixed(item.PPM+item.adjust, 1)+ 'dB'); } } // ------------------------------------ // ANALYSES AUDIO LEVELS OF EXPORTED FILE else if(fileExists(item.output_file)) { // ANALYSE OUTPUT FILE var levels = levelFileAnalyse( item.output_file , { "AdjustTargetType": (settings.AdjustTargetType == "Loudness" ? "LUFS" : settings.AdjustTargetType), "AdjustTargetLevel": settings.AdjustTargetLevel, "dialogGate": settings.Dial, "CalibrationLU": 0., "CalibrationPPM" : -9., // "AdjustLevelLowered": false, // "LimitThresholddB" : 0., }); // OUTPUT SPECS item.ttip.push('\nOUTPUT:'); item.ttip.push('- Loudness I : '+toFixed(levels.LUFS, 1)+ 'LUFS'); item.ttip.push('- Sample Peak : '+toFixed(levels.Peak, 1)+ 'dB'); item.ttip.push('- True Peak : '+toFixed(levels.TruePeak, 1)+ 'dBTP'); item.ttip.push('- PPM : '+toFixed(levels.PPM, 1)+ 'dB'); item.ttip.push('\n'); // ------------------------------------ // VALIDATE ADJUST const targetTypes = { 'loudness': { value: levels.LUFS, message: 'loudness' }, 'Peak': { value: levels.Peak, message: 'peak' }, 'TruePeak': { value: levels.TruePeak, message: 'true peak' }, 'PPM': { value: levels.PPM, message: 'PPM' } }; if (targetTypes[settings.AdjustTargetType]) { const tolerance = settings.tolerance ? settings.tolerance_val : 0.1; if (Math.abs(targetTypes[settings.AdjustTargetType].value - settings.AdjustTargetLevel) > tolerance) { err++; item.ttip.push(`Could not fix the file, ${targetTypes[settings.AdjustTargetType].message} of processed file is not in range`); } } // ------------------------------------ // VALIDATE SAMPLE PEAK LIMITER if (settings.peakLimit) { const peakChecks = { 'Peak': { value: levels.Peak, message: 'SamplePeak' }, 'TruePeak': { value: levels.TruePeak, message: 'TruePeak' } }; if (peakChecks[settings.peakLimit_type] && peakChecks[settings.peakLimit_type].value > settings.peakLimit_level + 0.1) { err++; item.ttip.push(`${peakChecks[settings.peakLimit_type].message} level exceeds limiter value`); } } } // ------------------------------------ else { err++; item.ttip.push('OUTPUT FILE WAS NOT CREATED'); } // ------------------------------------ // IF THERE ARE ERRORS if(err) { item.file_icon = "ExclamationTriangle"; item.file_icon_color = "FFFF0000"; item.file_status = "Failed"; } else if(wrn) { item.file_icon = "Square"; item.file_icon_color = "FFFF8800"; item.file_status = "Finished with warnings"; } else { item.file_icon = "CheckSquare"; item.file_icon_color = "FF00aa00"; item.file_status = "Done"; } _set(item); }); } // DONE WITH IT ALL setMainMessage('Done'); } // ============================================================================= // THE DIALOG FIELDS AND LAYOUT function dialogJSON(settings) { // CHECK OF ER EEN WARNING MOET KOMEN var warning = false; if(!settings.ffmpeg){ warning = "FFmpeg/FFprobe not installed. TruePeak Limiter is disabled."; } var spacer_size = 2; // return { // -------------------------------- "warning" : { "type" : "text", "label" : warning, "just" : "l", "visible" : (warning != false ? true : false), "bounds" : { "x": 0, "y" : 0, "w" : 600, }, }, // -------------------------------- "heading1" : { "type" : "text", "label" : "// ANALYSE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", "just" : "l", "bounds" : { "x": 0, "w" : 600, "h" : 25 }, }, // -------------------------------- "AdjustTargetLevel_label" : { "type" : "text", "label" : "target", "just" : "l", "bounds" : { "x": 20, "w" : 100, }, }, "AdjustTargetType" : { "type" : "combobox", "default" : settings.AdjustTargetType, "items" : "Loudness,TruePeak,Peak,PPM", "bounds" : { "y" : -1, "x": 180 , "w" : 120, }, "returns" : 0, }, "AdjustTargetLevel": { "type": "slider", "range" : { "min": -48, "max" : 12, "interval" : 1, "decimals" : 0 }, "bounds" : { "y" : -1, "x" : 305, "w" : 100, }, "value": settings.AdjustTargetLevel }, "AdjustTargetLevel_scale" : { "type" : "text", "label" : settings.AdjustTargetLevel_scale, "just" : "l", "bounds" : { "y" : -1, "x": 410, "w" : 200, }, }, // -------------------------------- "relativeGate_label" : { "type" : "text", "label" : "relative gate", "just" : "l", "bounds" : { "x": 20, "w" : 150, }, "enabled": true, }, "relativeGate": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "enabled": true, "toggle" : settings.relativeGate, }, "relativeGate_level": { "type": "slider", "range" : { "min": -48, "max" : 0, "interval" : 1, "decimals" : 0 }, "bounds" : { "y" : -1, "x" : 180, "w" : 100, }, "value": settings.relativeGate_level, "enabled": true, }, // -------------------------------- "dialogGate_label" : { "type" : "text", "label" : "dialog gate", "just" : "l", "bounds" : { "x": 20, "w" : 100, }, "enabled": true, }, "dialogGate": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "value" : settings.dialogGate, "enabled": true, "toggle" : false, }, // -------------------------------- "spacer1": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": spacer_size }, }, // -------------------------------- "heading2" : { "type" : "text", "label" : "// EXPORT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -", "just" : "l", "bounds" : { "x": 0, "w" : 600, "h" : 25 }, }, // -------------------------------- "export_label" : { "type" : "text", "label" : "export", "just" : "l", "bounds" : { "x": 20, "w" : 100, }, }, "export": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.export }, "exportMode" : { "type" : "combobox", "default" : settings.exportMode, "items" : "always,modified", "bounds" : { "y" : -1, "x": 180, "w" : 120, }, "returns" : 0, }, // -------------------------------- "exportRef_label" : { "type" : "text", "label" : "mode", "just" : "l", "bounds" : { "x": 20, "w" : 100, }, }, "exportRef_select" : { "type" : "combobox", "default" : settings.exportRef_select, "items" : "individual,loudest,reference", "bounds" : { "y" : -1, "x": 180, "w" : 120, }, "returns" : 0, }, "exportRef_reffile" : { "type" : "combobox", "default" : 'LUFS', "items" : settings.exportRef_reffile, "bounds" : { "y" : -1, "x": 305, "w" : 290, }, "returns" : 0, "visible" : false }, // -------------------------------- "tolerance_label" : { "type" : "text", "label" : "tolerance", "just" : "l", "bounds" : { "x": 20, "w" : 100, }, }, "tolerance": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.tolerance }, "tolerance_val": { "type": "slider", "visible": true, "range" : { "min": 0, "max" : 6, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x" : 180, "w" : 100, }, "value": settings.tolerance_val }, // -------------------------------- "peakLimit_label" : { "type" : "text", "label" : "peak limit", "just" : "l", "bounds" : { "x": 20, "w" : 160, }, }, "peakLimit": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.peakLimit }, "peakLimit_level": { "type": "slider", "visible": true, "range" : { "min": -99., "max" : 6, "interval" : .1, "decimals" : 1 }, "bounds" : { "y" : -1, "x" : 180, "w" : 100, }, "value": settings.peakLimit_level, }, "peakLimit_type" : { "type" : "combobox", "default" : settings.peakLimit_type, "items" : "Peak,TruePeak", "bounds" : { "y" : -1, "x": 300, "w" : 120, }, "returns" : 0, }, // -------------------------------- "spacer3": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": spacer_size }, }, // -------------------------------- "customFolder_label" : { "type" : "text", "label" : "custom folder", "just" : "l", "bounds" : { "x": 20, "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" : 420, }, "visible": false, }, // -------------------------------- "subFolder_label" : { "type" : "text", "label" : "sub folder", "just" : "l", "bounds" : { "x": 20, "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" : 350, }, "multiline" : false, "wordwrap" : false, "default" : settings.subFolder_val, "visible": true, }, // -------------------------------- "nametag_val_label" : { "type" : "text", "label" : "name tag", "just" : "l", "bounds" : { "x": 20, "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" : 420, }, "multiline" : false, "wordwrap" : false, "default" : settings.nametag_val, "visible": false, }, // -------------------------------- "spacer4": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": spacer_size }, }, // -------------------------------- "validate_label" : { "type" : "text", "label" : "validate", "just" : "l", "bounds" : { "x": 20, "w" : 160, }, }, "validate": { "type": "togglebox", "bounds" : { "y": -1, "x": 140, "w" : 25, }, "label" : "", "toggle" : settings.validate }, // -------------------------------- "spacer5": { "type": "text", "label": "","just": "c", "bounds": { "x": 20, "w": 550, "h": spacer_size }, }, // -------------------------------- "okay" : { "is_okay": true, "type" : "button", "label" : "ok", "bounds" : { "w" : 160, }, "returns" : 1, }, "cancel" : { "is_cancel": true, "type" : "button", "label" : "cancel", "bounds" : { "y": -1, "x": 180, "w" : 160 }, "returns" : 0, }, // -------------------------------- }; } // ============================================================================ // DIALOG CALLBACK function dialog_callback(props) { // ---------------- // UPDATE SETTINGS if (props["name"] == "exportMode") { settings['exportMode'] = props["text"]; } if (props["name"] == "exportRef_select") { settings['exportRef_select'] = props["text"]; } if (props["name"] == "exportRef_reffile") { settings['exportRef_reffile'] = props["text"]; } if (props["name"] == "peakLimit_type") { settings['peakLimit_type'] = props["text"]; } if (props["name"] == "export") { settings['export'] = props["toggle"]; } if (props["name"] == "customFolder") { settings['customFolder'] = props["toggle"]; } if (props["name"] == "nametag") { settings['nametag'] = props["toggle"]; } if (props["name"] == "subFolder") { settings['subFolder'] = props["toggle"]; } if (props["name"] == "peakLimit") { settings['peakLimit'] = props["toggle"]; } if (props["name"] == "lraLimit") { settings['lraLimit'] = props["toggle"]; } if (props["name"] == "tolerance") { settings['tolerance'] = props["toggle"]; } if (props["name"] == "relativeGate") { settings['relativeGate'] = props["toggle"]; } if (props["name"] == "AdjustTargetType"){ settings['AdjustTargetType'] = props["text"]; if(props["text"] == 'Loudness') { settings['AdjustTargetLevel_scale'] = "LUFS"; } else if(props["text"] == 'TruePeak') { settings['AdjustTargetLevel_scale'] = "dBTP"; } else if(props["text"] == 'Peak') { settings['AdjustTargetLevel_scale'] = "dBFS"; } else if(props["text"] == 'PPM') { settings['AdjustTargetLevel_scale'] = "PPM (0PPM = -9dBFS)"; } } // ---------------- // SHOW/HIDE WHEN EXPORT var exp = settings['export']; var ldn = settings['AdjustTargetType'] == "Loudness" ? 1 : 0; // ---------------- // UPDATE DIALOG dialog({ "tolerance_label" : { "enabled" : exp }, "tolerance" : { "enabled" : exp }, "tolerance_val" : { "enabled" : exp, "visible" : settings['tolerance'] }, "exportMode" : { "enabled" : exp, "default" : settings.exportMode }, "exportMode_label" : { "enabled" : exp, }, "exportRef_label" : { "enabled" : exp, }, "exportRef_select" : { "enabled" : exp, "default" : settings.exportRef_select }, "exportRef_reffile" : { "enabled" : exp, "default" : settings.exportRef_reffile, "visible" : settings.exportRef_select == 'reference' ? 1 : 0 }, "peakLimit_label" : { "enabled" : exp && ldn}, "peakLimit" : { "enabled" : exp && ldn }, "peakLimit_level" : { "enabled" : exp && ldn, "visible" : settings['peakLimit'] }, "peakLimit_type" : { "enabled" : exp && ldn, "visible" : settings['peakLimit'] && settings.ffmpeg, "default" : settings.peakLimit_type }, "lraLimit_label" : { "enabled" : exp && ldn }, "lraLimit" : { "enabled" : exp && ldn }, "lraLimit_level" : { "enabled" : exp && ldn, "visible" : settings['lraLimit'] }, "lraLimit_type" : { "enabled" : exp && ldn, "visible" : settings['lraLimit'] }, "customFolder_label" : { "enabled" : exp }, "customFolder" : { "enabled" : exp }, "customFolder_path" : { "enabled" : exp, "visible" : exp && settings['customFolder'] }, "subFolder_label" : { "enabled" : exp }, "subFolder" : { "enabled" : exp }, "subFolder_val" : { "enabled" : exp, "visible" : exp && settings['subFolder'] }, "nametag_val_label" : { "enabled" : exp }, "nametag" : { "enabled" : exp }, "nametag_val" : { "enabled" : exp, "visible" : exp && settings['nametag'] }, "validate_label" : { "enabled" : exp }, "validate" : { "enabled" : exp }, "relativeGate_label" : { "enabled" : ldn }, "relativeGate" : { "enabled" : ldn } , "relativeGate_level" : { "enabled" : ldn, "visible" : settings['relativeGate'] } , "dialogGate_label" : { "enabled" : ldn }, "dialogGate" : { "enabled" : ldn } , "AdjustTargetLevel_scale" : { "label" : settings['AdjustTargetLevel_scale'] } }); // ---------------- } // ============================================================================= // 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 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+'/'); } // ============================================================================= // STORE SETTINGS ON DRIVE function storeSettings(s) { var deffile = folders.content + gvar.pss + 'default.txt'; // Properties to remove ['returns', 'okay', 'cancel', 'exportRef_reffile'].forEach(prop => delete s[prop]); writeFile(deffile, JSON.stringify(s, null, 2)); } // ============================================================================= // LOAD SETIINGS function loadSettings(settings) { var deffile = folders.content + gvar.pss + 'default.txt'; if (fileExists(deffile)) { var defaults = JSON.parse(readFile(deffile)); Object.assign(settings, defaults); } return settings; } // ============================================================================= // DEBUG HELPER function _e(...args) { var output = []; args.forEach((arg) => { if (typeof arg === "object") { output.push(JSON.stringify(arg, null, 2)); } else { output.push(arg); } }); echo(output.join("\n")); } // ============================================================================= // SET HELPER function _set(obj) { if(!obj) return; if(!obj.path) return; let path = obj.path; let index = obj.index || 0; // if file_icons exists, set the icon if(obj.file_icon) setFileIcon(path, index, obj.file_icon); // if file_icon_color exists, set the icon color if(obj.file_icon_color) setFileIconColor(path, index, obj.file_icon_color); // if file_status exists, set the status if(obj.file_status) setFileStatus(path, index, obj.file_status); // if file_tooltip exists, set the tooltip // if(obj.file_tooltip) setFileTooltip(path, index, obj.file_tooltip); // if ttip exists, implode the list and set the tooltip if(obj.ttip) setFileTooltip(path, index, obj.ttip.join("\n")); } // ============================================================================= // LOAD DEFAULT VALUES function hasUserCanceled(fileCount = false) { if(isCanceled()){ setProgress(0); setMainMessage("- Cancelled -"); if(fileCount !== false){ for (i = fileCount; i < files.length; i++) { var item = files[i]; item.file_icon = "ExclamationCircle"; item.file_icon_color = "FFff0000"; item.file_status = "Cancelled"; _set(item); } } abort(); } }