header={ "chef": "BeatRig", "dependencies": "ffmpeg,afconvert,ffprobe", "title": "Album Master", "description": "Create music master deliverables.", "instructions": "", "recipe_version": "0.394", "tags": "Audio", "type": "default", "os": "windows,macOS", "palette": "Clean Slate", "spice": "BQ==:G+Hy36kJcnQQYYavb/JV2Il6eCYr6h4hRJHCF9rRbhX5BcazL6cuTgUMdz0xA50VWA5PmmV0Q7aWkiUtNFtbk991l+t0e8IBXMReMIT1GNjnZZDv7n2YRAo0xcSQbY0ixjG6LLH4wGKnspKAoA/v69iLn6N2IH0XzIZwTGdHSo4=", "flavour": "E3LWc9pL/h2b4EuoT8ULHsJHyrhuYqn3w5B7BEDLnEgxZma5aE8njC+Y9U4AsB4VJyqQXel3ffm2eOYHwvZ3AP9Qru+t0yJGu7uSs8mvWUdRnw87D0Vnl00/YRJDELE6mHkwAxXh89Y5+hCZOrvzFNIAIBx4YR5WSZrz5IBydnE=", "time": 1737203663, "core_version": "0.7.2", "magnetron_version": "1.0.333", "functions": "main", "uuid": "a7b150f1d3e14bc7b3da4baecfc491c7", "ignore_dependencies": "afconvert" }; // GLOBAL FOR THE DURATION OF THE CURRENT FILE var curDuration = 0; // GLOBAL TO INDICATE IF FFMPEG CAN HANDLE LIBSOXR RESAMPLING var libsoxr = false; // GLOBAL TO INDICATE IF AFCONVCERT IS AVAILABLE var afconvert = false; var files = []; // ============================================================================= function main() { // ------------------------------------ // CHECK IF FFMPEG IS AVAILABLE if(getAllowedApps("ffmpeg") == '') { setMainMessage(""); abort("Could not start FFMPEG. Install it by from the settings." ); } // ------------------------------------ // CHECK IF FFPROBE IS AVAILABLE if(getAllowedApps("ffprobe") == '') { setMainMessage(""); abort("Could not start FFPROBE. Install it by from the settings." ); } // ------------------------------------ // CHECK IF FFMPEG CONTAINS LIBSOXR libsoxr = cmd("ffmpeg", ['-version']); libsoxr = libsoxr.includes('--enable-libsoxr'); // ------------------------------------ // CHECK IF AFCONVERT IS AVAILABLE if( getAllowedApps("afconvert") != '' ) { afconvert = true; } else { // TRY TO INSTALL if (gvar.isWindows == 0) { var loc = gvar.pss+'usr'+gvar.pss+'bin'+gvar.pss+'afconvert'; if(fileExists(loc)) { setAllowedApps('afconvert', loc); afconvert = true; } } } // ------------------------------------ // LOUDNES SPECS var album_max_lufs = -99.; var album_max_tp = -99.; var adjust = 0.; var fileTypes = [ 'wav', 'aif', 'aiff', ]; // ------------------------------------ setMainMessage("analysing"); setProgress(0); // ------------------------------------ // CLEANUP THE LIST AND CHECK EXTENSION files = getFiles(); var failed = 0; for (let i=0;i 0){ abort("One or more files could not be analysed."); return; } // ------------------------------------ // GET ALL FILES FROM THE APP files = getFiles(); // CHECK IF THERE IS AT LEAST ONE FILE if(files.length < 1){ abort("Please add some files first."); setMainMessage(""); return; } // ------------------------------------ // START PATH FOR DIALOG var path = getPathInfo( files[0].path ); // ------------------------------------ // DIALOG var r = showDialog(path.folder); // ------------------------------------ // IF CANCELLED if(r.cancel){ setMainMessage(""); return; } // ------------------------------------ // ANALYSE LOUDNESS OF ALL AUDIO FILES INDIVIDUALLY var args = { "AdjustTargetType": "LUFS", "AdjustTargetLevel": 0. }; // ------------------------------------ // ANALYSE FILES var maxLoudFile = 0; var maxPeakFile = 0; for (let i=0;i album_max_lufs) maxLoudFile = i; if(fileLevels.TruePeak > album_max_tp) maxPeakFile = i; album_max_lufs = Math.max(fileLevels.LUFS, album_max_lufs ); album_max_tp = Math.max(fileLevels.TruePeak, album_max_tp ); // SET STATUS files[i]['file_icon_color'] = "FFBBBBBB"; files[i]['file_icon'] = "square"; files[i]['file_status'] = ''; // LEVELS files[i]['lufs'] = Math.round( fileLevels.LUFS * 100. ) / 100.; files[i]['peaks'] = Math.round( fileLevels.TruePeak * 100. ) / 100.; files[i]['plr'] = Math.round( fileLevels.PeakLoudness * 100. ) / 100.; // SPECS files[i]['duration'] = specs.duration; var durationHuman = humanTime(specs.duration); files[i]['durationHuman'] = durationHuman.time; files[i]['codec'] = specs.codec_name; files[i]['samplerate'] = specs.sample_rate; files[i]['channels'] = specs.channels; files[i]['bitrate'] = specs.bits_per_sample; // CHECK SOURCE FILE files[i]['sourceSpecsValid'] = ( files[i].samplerate == 176400 && files[i].channels == 2 && files[i].codec == "pcm_s24le" && toLowerCase(path.ext) == 'wav' ? true : false); setFiles(files); } // ------------------------------------ // CALCULATE REF FILE AND ADJUSTMENT var refFile = maxLoudFile; adjust = -14. - album_max_lufs; // CHECK IF ADJUSTING IS NOT CAUSING CLIPPING if( album_max_tp + adjust > -1. ){ refFile = maxPeakFile; adjust = -1.1 - album_max_tp; } // UPDATE LIST files = getFiles(); files[refFile]['file_icon_color'] = "FFBBBBBB"; files[refFile]['file_icon'] = "square"; files[refFile]['file_status'] = '(REF. FILE)'; setFiles(files); // ------------------------------------ // SET THE TOOLTIPS files = getFiles(); for (let i=0;i 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) }; } // ============================================================================= // FILE SPECS function filespecs(file) { // FFPROBE COMMAND const ffprobe = cmd("ffprobe", [ "-v", "error", "-print_format", "json", "-show_entries", "stream=codec_type,codec_name,bit_rate,channels,sample_rate,bits_per_sample : format=duration,size", "-i", file, ]); // Parse JSON output const specs = JSON.parse(ffprobe); // Extract audio stream information const audioStream = (specs.streams || []).find(stream => stream.codec_type === "audio"); // If there is only one stream, return the audio stream information if( specs.streams.length == 1 && audioStream) { return { codec_name: audioStream?.codec_name || null, channels: parseInt(audioStream?.channels) || 0 , sample_rate: parseInt(audioStream?.sample_rate) || 0, duration: parseFloat(specs.format?.duration) || 0, bits_per_sample: parseInt(audioStream?.bits_per_sample) || 0, bitrate: parseInt(audioStream?.bit_rate) || 0, filesize: parseInt(specs.format?.size) || 0, }; } else { return false; } } // ============================================================================= function humanTime(totalSeconds) { var ms = (totalSeconds - Math.floor(totalSeconds))*1000; var totalMinutes = Math.floor(totalSeconds / 60); var seconds = Math.floor(totalSeconds % 60); var hours = Math.floor(totalMinutes / 60); var minutes = totalMinutes % 60; return { h: parseInt(hours), m: parseInt(minutes), s: parseInt(seconds), ms : parseInt(ms), time : pad(hours,2) + ":" + pad(minutes,2) + ":"+ pad(seconds,2) + "."+ pad(ms,3), }; } // ============================================================================= function pad(num, size) { num = toFixed(parseInt(num), 0); while (num.length < size) num = "0" + num; return num; } // ============================================================================ function dialog_callback(props) { // dummy }