iZotope RX Batch Processor Inefficiencies
Title
iZotope RX Batch Processor Inefficiencies
What do you expect to happen when you run the script/macro?
This script will apply an iZotope RX Batch Process Preset to the selected folder in finder
Are you seeing an error?
What happens when you run this script?
Things seem to working surprisingly well, there are some inefficiencies however that i can't seem to work around. There are a few instances where I need to click a UI element and the action is performed correctly, but throws an error. To mitigate this, I've added ({ onError: "Continue" }); in a few places. This slows down things quite a bit. I was wondering if there were a work around for this. There's also the inability to "CMD+A" to select all audio files after you've navigated to the selected folder during the importing of audio to RX. If anyone has some suggestion I'd love to hear them!
How were you running this script?
I used a Stream Deck button
How important is this issue to you?
5
Details
{ "inputExpected": "This script will apply an iZotope RX Batch Process Preset to the selected folder in finder", "inputIsError": false, "inputWhatHappens": "Things seem to working surprisingly well, there are some inefficiencies however that i can't seem to work around. There are a few instances where I need to click a UI element and the action is performed correctly, but throws an error. To mitigate this, I've added ({ onError: \"Continue\" }); in a few places. This slows down things quite a bit. I was wondering if there were a work around for this. There's also the inability to \"CMD+A\" to select all audio files after you've navigated to the selected folder during the importing of audio to RX. If anyone has some suggestion I'd love to hear them! ", "inputHowRun": { "key": "-MpfwmPg-2Sb-HxHQAff", "title": "I used a Stream Deck button" }, "inputImportance": 5, "inputTitle": "iZotope RX Batch Processor Inefficiencies" }
Source
function getPaths(selectedFolder, allAudioPaths) {
const audioPath = sf.file.directoryGetFiles({
searchPattern: "*" + '.wav',
path: selectedFolder,
isRecursive: true,
}).paths
audioPath.forEach(audioPath => {
allAudioPaths.push(audioPath)
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////
function setBatchProcessorPreset({ presetName, batchProcessorWin }) {
const izotope = sf.ui.izotope;
const presetPopupBtn = batchProcessorWin.popupButtons.first;
if (!batchProcessorWin.exists) {
izotope.menuClick({
menuPath: ["Window", "Batch Processor"],
targetValue: "Enable"
});
batchProcessorWin.elementWaitFor();
batchProcessorWin.windowRestore();
}
//Set the Batch Processor Preset
presetPopupBtn.value.value = presetName;
//Regex removes keyboard shortcut displayed inside square brackets
if (presetPopupBtn.value.invalidate().value && presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) {
while (presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) {
presetPopupBtn.elementClick({ actionName: "AXMenuPress" });
sf.keyboard.press({ keys: "down" });
sf.keyboard.press({ keys: "return" });
sf.wait({ intervalMs: 100 });
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
function batchProcessInizotope(path, presetName) {
const izotope = sf.ui.izotope;
const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first;
setBatchProcessorPreset({ presetName, batchProcessorWin });
// Cannot CMD + A to select all files after navigating to selected folder
// Current workaround is select files instead and navigate directly to each selected file
for (var i = 0; i < path.length; i++) {
batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementClick({ onError: "Continue" });
let searchWin = izotope.windows.whoseTitle.is("Select audio files").first
searchWin.elementWaitFor();
sf.keyboard.type({ text: '/' });
searchWin.sheets.first.elementWaitFor();
searchWin.sheets.first.textFields.first.elementSetTextAreaValue({ value: path[i] });
sf.keyboard.press({ keys: "return", });
searchWin.sheets.first.elementWaitFor({ waitType: "Disappear" });
let searchWinOpenButton = searchWin.buttons.whoseTitle.is("Open").first
searchWinOpenButton.elementClick();
}
batchProcessorWin.buttons.whoseDescription.is("Process").first.elementClick();
while (batchProcessorWin.buttons.whoseDescription.is("Pause").first.exists) {
sf.wait({ intervalMs: 100 });
}
batchProcessorWin.buttons.whoseDescription.is("Remove all files").first.elementClick({ onError: "Continue" });
log(`${presetName} Applied`)
}
////////////////////////////////////////////////////////////////////////////////////////////////////
function main() {
if (!sf.ui.izotope.isRunning) {
throw `Please Launch iZotope izotope 10`;
}
sf.ui.izotope.appEnsureIsRunningAndActive();
sf.ui.izotope.mainWindow.elementWaitFor();
let selectedFolder = sf.ui.finder.selectedPaths[0]
let allAudioPaths = []
getPaths(selectedFolder, allAudioPaths);
batchProcessInizotope(allAudioPaths, 'TEST');
}
main();
Links
User UID: EoVu20w2ZRTvmJvgozc57ZXuAhU2
Feedback Key: sffeedback:EoVu20w2ZRTvmJvgozc57ZXuAhU2:-NlPIcPSgGXGkSTx6dt6
Feedback ZIP: G16KUKwEvec0n+BjMbRd6AQYR7HlApQbtjMAiZwwzXzYXrRNFJhbTSbUrsr5tv04n999Dh+0HFicQniaZy+L6uR2c/07ZRa7/wLiuVCBYs5P7h1fx81AYzprRHihRd2uQaFjORw/u57B+evgCHb/7Gxhq8c5Imu9Ef4EKHSH2EnskBSLXtBWKOQ4/YChxwUKKME8vGt28TCPUskjS/CY2tqxaJnMQ7tj2cZndLQZKEKxwqpV6uaRrXEUkQ3uzkkp4F2vYGMfKaEmKRNS4vb4sMKp6n/HXRbnqnihBG69ZGmjQ7wK2ahea7Wlp6Knm9OUpeNp8sQ0ikBeaYIzoW0l0Okcu+vu5IWweJK05KDnIGo=
Linked from:
- FForrester Savell @Forrester_Savell
Hey @nathansalefski
I finally go around to getting a Batch Processor script written and saw you had this. Mine currently works, is a little different to yours. I plan on accessing mine through a Command ID, so will eventually feed info into the the globals at the top when its finished.
The part that is stumping me is that how do I confirm that the file processing is complete? Say I want to run multiple presets (convert to different sample/bit rates etc). How would it determine if the conversion on one preset is done, to then allow it to move on to the other?
I'm going to tag @Kitch here too, because there's a weird bug happening when I try to access the 'Open Window' within RX.
Error invoking AXElement: kAXErrorAttributeUnsupported (Izotope RX10 Batch Convert: Line 74)
SoundFlow.Shortcuts.AXUIElementException: kAXErrorAttributeUnsupported
at SoundFlow.Shortcuts.AXUIElement.DoAction(String action) + 0x98
at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.d__11.MoveNext() + 0x7cI've had to capture them in try-catch blocks to work, as despite them correctly opening, they throw the same error I've flagged here:
Selecting first cell in Bus tab of I/O Window causes ProTools to hard crash/exit.however the
mouseClickElement()
doesn't work on this one.Here's my code:
const testFolderPath = '/Volumes/AudioDrive/SomeFolder/SomeSubFolder'; //enter a path here const testFiles = ['file1.wav', 'file2.wav',]; //identify the files to be processed here let menuPath = ['Batch Processor']; const batchPresetName = '44.1khz_16bit' // Bail out if iZotope is not running if (!sf.ui.izotope.isRunning) throw `iZotope RX is not running`; function activateAndWaitForiZotope() { const izotope = sf.ui.izotope; izotope.appActivate(); let tries = 0; while (!sf.ui.frontmostApp.title.value.includes("iZotope") && tries < 20) { sf.wait({ intervalMs: 100 }); tries++; } if (tries === 20) throw `Could not activate iZotope RX`; } function loadPreset(presetName) { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; const presetPopupBtn = batchProcessorWin.popupButtons.first; activateAndWaitForiZotope(); if (!batchProcessorWin.exists) { izotope.menuClick({ menuPath: ["Window", "Batch Processor"], targetValue: "Enable" }); batchProcessorWin.elementWaitFor(); batchProcessorWin.windowRestore(); } // Set the Batch Processor Preset presetPopupBtn.value.value = presetName; // Regex removes keyboard shortcut displayed inside square brackets if (presetPopupBtn.value.invalidate().value && presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) { while (presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) { presetPopupBtn.elementClick({ actionName: "AXMenuPress" }); sf.keyboard.press({ keys: "down" }); sf.keyboard.press({ keys: "return" }); sf.wait({ intervalMs: 100 }); } } } function setCustomLocation() { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; const customLocationBtn = batchProcessorWin.popupButtons.allItems[2]; // Extract the parent folder name from the testFolderPath const parentFolderName = testFolderPath.split('/').filter(part => part.length > 0).slice(-1)[0]; const newFolderName = `${parentFolderName} ${batchPresetName}`; // Open the custom location dropdown menu customLocationBtn.elementClick({ actionName: "AXMenuPress" }); sf.keyboard.press({ keys: "down" }); sf.keyboard.press({ keys: "return" }); try { batchProcessorWin.buttons.whoseDescription.is("Choose location...").first.elementClick(); sf.ui.izotope.windows.whoseTitle.is("Open").first.elementWaitFor({ waitType: 'Appear' }); } catch (err) { log('Dealing with Open Window load bug'); } navigateTo(testFolderPath); const openWin = izotope.windows.whoseTitle.is("Open").first; // Check if the folder already exists const folderRows = openWin.splitGroups.first.splitGroups.first.scrollAreas.first.children.whoseRole.is("AXOutline").whoseDescription.is("list view").first.children.whoseRole.is("AXRow").allItems; let folderExists = false; folderRows.forEach(row => { try { const folderNameElement = row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement"); if (folderNameElement.exists && folderNameElement.value.value === newFolderName) { folderExists = true; // Select the existing folder row.children.whoseRole.is("AXCell").first.mouseClickElement(); log(`Folder '${newFolderName}' already exists. Selecting it.`); } } catch (error) { log(`Warning: Failed to check folder existence: ${error.message}`); } }); if (!folderExists) { // Create a new folder if it doesn't exist try { openWin.buttons.whoseTitle.is("New Folder").first.elementClick(); openWin.buttons.whoseTitle.is("New Folder").first.elementWaitFor({}); } catch (err) { log('Open Win Bug?'); } try { openWin.sheets.first.textFields.first.elementSetTextFieldWithAreaValue({ value: newFolderName }); openWin.sheets.first.buttons.whoseTitle.is("Create").first.elementClick(); log(`Created new folder: ${newFolderName}`); } catch (err) { log('Failed to create a new folder: ' + err.message); } } // Click open openWin.buttons.whoseTitle.is("Open").first.elementClick(); } // This will be a referenced package once testing complete function navigateTo(path) { //Get a reference to the focused window in the frontmost app: var win = sf.ui.frontmostApp.focusedWindow; //Open the Go to... sheet sf.keyboard.type({ text: '/' }); //Wait for the sheet to appear var sheet = win.sheets.first.elementWaitFor({ timeout: 500 }, 'Could not find "Go to" sheet in the Open dialog').element; if (sheet.comboBoxes.first.exists) { // Set the folder path in the combo box sheet.comboBoxes.first.value.value = path; sheet.buttons.whoseTitle.is('Go').first.elementClick(); } else { // Set the path in the text field sheet.textFields.first.value.value = path; sf.keyboard.press({ keys: 'return' }); } //Wait for sheet to close win.sheets.first.elementWaitFor({ waitForNoElement: true, timeout: 500 }, '"Go to" sheet didn\'t close in time'); } function openFiles(files) { try { // Get the list view that contains the files const fileListView = sf.ui.izotope.windows.whoseTitle.is("Select audio files").first .splitGroups.first.splitGroups.first.scrollAreas.first .children.whoseRole.is("AXOutline").whoseDescription.is("list view").first; if (!fileListView.exists) { throw new Error("File list view not found"); } // Get all the rows representing files in the list const fileRows = fileListView.children.whoseRole.is("AXRow").allItems; if (fileRows.length === 0) { log("No files found in the list view."); return; } // Loop through each row and select rows that match any item in the files array fileRows.forEach(row => { try { // Get the title element which contains the file name const fileNameElement = row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement"); if (!fileNameElement.exists) { log("File name element not found for row, skipping..."); return; } const fileName = fileNameElement.value.value; // Check if the fileName matches any value in the files array if (files.includes(fileName)) { // Select the row (use Command key to multi-select) row.children.whoseRole.is("AXCell").first.mouseClickElement({ isCommand: true // Use Command for multi-selection }); log(`Selected file: ${fileName}`); } } catch (error) { log(`Warning: Failed to select file: ${error.message}`); } }); // Click the "Open" button to confirm the selection of files sf.ui.izotope.windows.whoseTitle.is("Select audio files").first.buttons.whoseTitle.is("Open").first.elementClick(); } catch (error) { log(`Error in openFiles function: ${error.message}`); } } function main() { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; activateAndWaitForiZotope(); if (!batchProcessorWin.exists) { izotope.menuClick({ menuPath: ["Window", "Batch Processor"], targetValue: "Enable" }); batchProcessorWin.elementWaitFor(); batchProcessorWin.windowRestore(); } // Clear the File list first batchProcessorWin.buttons.whoseDescription.is("Remove all files").first.elementClick(); // Open add files window try { batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementClick(); batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementWaitFor({ waitType: "Appear" }); } catch (err) { log('Open Window Bug?'); } // Navigate to the folder containing the files navigateTo(testFolderPath); // Open the files that match the testFiles array openFiles(testFiles); // Load the preset for converting the imported files loadPreset(batchPresetName); // Set the custom location for exporting setCustomLocation(); // Process!! batchProcessorWin.buttons.whoseDescription.is("Process").first.elementClick(); } main();
Kitch Membery @Kitch2024-10-10 01:38:24.686Z2024-10-10 02:22:31.926Z
Re the issue... Try something like this this. :-)
sf.ui.izotope.appActivate(); const batchProcessorWindow = sf.ui.izotope.getFirstWithTitle("Batch Processor") batchProcessorWindow.invalidate(); // Here I'm using the find method to get the first button with description "Choose location..." or with a height of 36 (the width fluctuates) to match the "Choose Location..." button. const chooseLocationButton = batchProcessorWindow.buttons.find(btn => btn.getString("AXDescription") === "Choose location..." || (btn.frame.h === 36) ); // Added `asyncSwallow:true` as it seems that iZotope is not reporting the existence of the button despite the button actually being clicked. chooseLocationButton.elementClick({ asyncSwallow: true }); const openWindow = sf.ui.izotope.windows.whoseTitle.is("Open").first; // Waiting for the "Open" window to make sure the above `elementClick()` worked. openWindow.elementWaitFor();
UPDATED
- FForrester Savell @Forrester_Savell
Hey @Kitch
I think I've given you a misdirect with the error line number, it was from previous code. The window I'm having trouble with is the macOs 'Open' file window. e.g. related to 'Add Files' or to the 'Choose location...', (which I managed to access adapting your original code for choosing the Batch Preset.)
The issue is the 'Open' window, in that my code opens it fine in any case, but without the try-catch block it throws the error. I've also got other issues in keeping that window focused that have only appeared now that I'm accessing this 'Batch Process' script via a Command ID, which weren't happening with the code I posted above. Maybe we can talk about that today on a Zoom?
Below is the specific parts of the script that relate to the Open Window.
// Open add files window try { batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementClick(); batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementWaitFor({ waitType: "Appear" }); } catch (err) { log('Open Window Bug?'); }
try { batchProcessorWin.buttons.whoseDescription.is("Choose location...").first.elementClick(); sf.ui.izotope.windows.whoseTitle.is("Open").first.elementWaitFor({ waitType: 'Appear' }); } catch (err) { log('Dealing with Open Window load bug'); }
- In reply toForrester_Savell⬆:
Kitch Membery @Kitch2024-10-10 02:14:20.193Z
When you click the "Process" button to process a module chain in the Batch Window, the "Process" button will change to a "Cancel" button. When the processing has finished, the "Cancel" button will disappear and will be replaced by the "Process" button.
So you could do something like this...
const batchProcessorWindow = sf.ui.izotope.getFirstWithTitle("Batch Processor"); // Click Process. batchProcessorWindow.getFirstWithDescription("Process").elementClick(); // Wait for the Cancel button to appear. batchProcessorWindow.getFirstWithDescription("Cancel").elementWaitFor(); // Wait for the Process button to return sf.waitFor({ callback: () => batchProcessorWindow.getFirstWithDescription("Process").exists, timeout: -1, pollingInterval: 300, }); // Log "Done" when the "Process" button returns log("Done");
- FForrester Savell @Forrester_Savell
Thanks so much for looking into this @Kitch !
Can't wait to try these out. I'll report back with the results.
Kitch Membery @Kitch2024-10-10 02:24:10.500Z
Nice! Keep me posted. I'm in Kingscliff atm so I may reach out tomorrow for a Zoom if you are free.
- In reply toForrester_Savell⬆:
Kitch Membery @Kitch2024-10-11 04:05:06.171Z
Hi Forrester,
Along with the other adjustments we spoke about, try replacing the
openFiles
function with this...function openFiles(files) { // Get the list view that contains the files const selectAudioFiles = sf.ui.izotope.windows.whoseTitle.is("Select audio files").first; const fileListView = selectAudioFiles.splitGroups.first.splitGroups.first.scrollAreas.first .children.whoseRole.is("AXOutline").whoseDescription.is("list view").first; const screenSize = sf.ui.screens.allScreens[0].size; selectAudioFiles.windowMove({ position: { x: 0, y: 0 }, size: screenSize, }); fileListView.elementWaitFor(); if (!fileListView.exists) { throw new Error("File list view not found"); } // Get all the rows representing files in the list const fileRows = fileListView.children.whoseRole.is("AXRow").allItems; if (fileRows.length === 0) { log("No files found in the list view."); throw new Error("No files found in the list view. Aborting the process."); } // Loop through each row and select rows that match any item in the files array fileRows.forEach((row, index) => { // Return if the first row AXTitleUIElement returns an error due to it being the column header. if (index === 0 && !row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement").exists) return // Get the title element which contains the file name const fileNameElement = row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement"); const fileName = fileNameElement.value.value; // Check if the fileName matches any value in the files array if (files.includes(fileName)) { // Select the row (use Command key to multi-select) row.children.whoseRole.is("AXCell").first.mouseClickElement({ isCommand: true // Use Command for multi-selection }); } }); // Click the "Open" button to confirm the selection of files. selectAudioFiles.buttons.whoseTitle.is("Open").first.elementClick(); // Wait for window to disappear selectAudioFiles.elementWaitFor({ waitForNoElement: true }); }
- FForrester Savell @Forrester_Savell
Hey @Kitch
No luck there unfortunately. Despite making the Open Win full screen, it fails on the last line
// Wait for window to disappear selectAudioFiles.elementWaitFor({ waitForNoElement: true })
I can't see it selecting the files (i.e. highlighting blue) and the 'Open' button remains greyed out.
However, as with the script I gave you, it works great in the isolated non-Command ID version.
Kitch Membery @Kitch2024-10-11 04:56:16.675Z
What happens when you remove that line?
- FForrester Savell @Forrester_Savell
Removed that line and it just continued on with the script, but hadn't added the files to be processed. i.e. it went ahead and interacted with Choose location, created a folder etc., got right to the end to click 'Process' but there were no files to process.
Kitch Membery @Kitch2024-10-11 05:05:39.278Z
Cool thanks, back to the drawing board.
- In reply toForrester_Savell⬆:
Kitch Membery @Kitch2024-10-11 05:27:04.800Z
The
setCustomLocation
function needs the updates we made this morning but try this. It is working for me. Fingers crossed!Require Script
/* @ts-ignore */ const { processBatch } = require("ADD COMMAND ID HERE"); const folderPath = 'ADD BOUNCE DIRECTORY HERE'; const filesToProcess = ["One.wav", "Two.wav", "Three.wav"]; // Change this to the file names. const batchPresetName = '44.1khz_16bit' processBatch(folderPath, filesToProcess, batchPresetName);
Export Script
// Bail out if iZotope is not running if (!sf.ui.izotope.isRunning) throw `iZotope RX is not running`; function activateAndWaitForiZotope() { const izotope = sf.ui.izotope; izotope.appActivate(); let tries = 0; while (!sf.ui.frontmostApp.title.value.includes("iZotope") && tries < 20) { sf.wait({ intervalMs: 100 }); tries++; } if (tries === 20) throw `Could not activate iZotope RX`; } function loadPreset(presetName) { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; const presetPopupBtn = batchProcessorWin.popupButtons.first; activateAndWaitForiZotope(); if (!batchProcessorWin.exists) { izotope.menuClick({ menuPath: ["Window", "Batch Processor"], targetValue: "Enable" }); batchProcessorWin.elementWaitFor(); batchProcessorWin.windowRestore(); } // Set the Batch Processor Preset presetPopupBtn.value.value = presetName; // Regex removes keyboard shortcut displayed inside square brackets if (presetPopupBtn.value.invalidate().value && presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) { while (presetPopupBtn.value.invalidate().value.replace(/ \[[^\]]*\]$/, '') !== presetName) { presetPopupBtn.elementClick({ actionName: "AXMenuPress" }); sf.keyboard.press({ keys: "down" }); sf.keyboard.press({ keys: "return" }); sf.wait({ intervalMs: 100 }); } } } function setCustomLocation(folderPath, batchPresetName) { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; const customLocationBtn = batchProcessorWin.popupButtons.allItems[2]; // Extract the parent folder name from the testFolderPath const parentFolderName = folderPath.split('/').filter(part => part.length > 0).slice(-1)[0]; const newFolderName = `${parentFolderName} ${batchPresetName}`; // Open the custom location dropdown menu customLocationBtn.elementClick({ actionName: "AXMenuPress" }); sf.keyboard.press({ keys: "down" }); sf.keyboard.press({ keys: "return" }); try { batchProcessorWin.buttons.whoseDescription.is("Choose location...").first.elementClick(); sf.ui.izotope.windows.whoseTitle.is("Open").first.elementWaitFor({ waitType: 'Appear' }); } catch (err) { log('Dealing with Open Window load bug'); } navigateTo(folderPath); const openWin = izotope.windows.whoseTitle.is("Open").first; // Check if the folder already exists const folderRows = openWin.splitGroups.first .splitGroups.first.scrollAreas.first. children.whoseRole.is("AXOutline").whoseDescription.is("list view").first // Get all the rows representing files in the list const fileRows = folderRows.children.whoseRole.is("AXRow").allItems; let folderExists = false; fileRows.forEach(row => { try { const folderNameElement = row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement"); if (folderNameElement.exists && folderNameElement.value.value === newFolderName) { folderExists = true; // Select the existing folder row.children.whoseRole.is("AXCell").first.mouseClickElement(); log(`Folder '${newFolderName}' already exists. Selecting it.`); } } catch (error) { log(`Warning: Failed to check folder existence: ${error.message}`); } }); if (!folderExists) { // Create a new folder if it doesn't exist try { openWin.buttons.whoseTitle.is("New Folder").first.elementClick(); openWin.buttons.whoseTitle.is("New Folder").first.elementWaitFor({}); } catch (err) { log('Open Win Bug?'); } try { openWin.sheets.first.textFields.first.elementSetTextFieldWithAreaValue({ value: newFolderName }); openWin.sheets.first.buttons.whoseTitle.is("Create").first.elementClick(); log(`Created new folder: ${newFolderName}`); } catch (err) { log('Failed to create a new folder: ' + err.message); } } // Click open openWin.buttons.whoseTitle.is("Open").first.elementClick(); } // This will be a referenced package once testing complete function navigateTo(path) { //Get a reference to the focused window in the frontmost app: var win = sf.ui.frontmostApp.focusedWindow; //Open the Go to... sheet sf.keyboard.type({ text: '/' }); //Wait for the sheet to appear var sheet = win.sheets.first.elementWaitFor({ timeout: 500 }, 'Could not find "Go to" sheet in the Open dialog').element; if (sheet.comboBoxes.first.exists) { // Set the folder path in the combo box sheet.comboBoxes.first.value.value = path; sheet.buttons.whoseTitle.is('Go').first.elementClick(); } else { // Set the path in the text field sheet.textFields.first.value.value = path; sf.keyboard.press({ keys: 'return' }); } //Wait for sheet to close win.sheets.first.elementWaitFor({ waitForNoElement: true, timeout: 500 }, '"Go to" sheet didn\'t close in time'); } function openFiles(files) { // Get the list view that contains the files const selectAudioFiles = sf.ui.izotope.windows.whoseTitle.is("Select audio files").first; const fileListView = selectAudioFiles.splitGroups.first.splitGroups.first.scrollAreas.first .children.whoseRole.is("AXOutline").whoseDescription.is("list view").first; const screenSize = sf.ui.screens.allScreens[0].size; selectAudioFiles.windowMove({ position: { x: 0, y: 0 }, size: screenSize, }); fileListView.elementWaitFor(); if (!fileListView.exists) { throw new Error("File list view not found"); } // Get all the rows representing files in the list const fileRows = fileListView.children.whoseRole.is("AXRow").allItems; if (fileRows.length === 0) { log("No files found in the list view."); throw new Error("No files found in the list view. Aborting the process."); } // Loop through each row and select rows that match any item in the files array fileRows.forEach((row, index) => { // Return if the first row AXTitleUIElement returns an error due to it being the column header. if (index === 0 && !row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement").exists) return // Get the title element which contains the file name const fileNameElement = row.children.whoseRole.is("AXCell").first.getElement("AXTitleUIElement"); const fileName = fileNameElement.value.value; // Check if the fileName matches any value in the files array if (files.includes(fileName)) { // Select the row (use Command key to multi-select) row.children.whoseRole.is("AXCell").first.mouseClickElement({ isCommand: true // Use Command for multi-selection }); } }); // Click the "Open" button to confirm the selection of files. selectAudioFiles.buttons.whoseTitle.is("Open").first.elementClick(); // Wait for window to disappear selectAudioFiles.elementWaitFor({ waitForNoElement: true }); } function processBatch(folderPath, filesToProcess, batchPresetName) { const izotope = sf.ui.izotope; const batchProcessorWin = izotope.windows.whoseTitle.is("Batch Processor").invalidate().first; activateAndWaitForiZotope(); if (!batchProcessorWin.exists) { izotope.menuClick({ menuPath: ["Window", "Batch Processor"], targetValue: "Enable" }); batchProcessorWin.elementWaitFor(); batchProcessorWin.windowRestore(); } // Clear the File list first batchProcessorWin.buttons.whoseDescription.is("Remove all files").first.elementClick(); // Open add files window batchProcessorWin.buttons.whoseDescription.is("Add files").first.elementClick({ asyncSwallow: true }); // Wait for "Select audio files" window sf.ui.izotope.windows.whoseTitle.is("Select audio files").first.elementWaitFor(); // Navigate to the folder containing the files navigateTo(folderPath); // Open the files that match the filesToProcess array openFiles(filesToProcess); // Load the preset for converting the imported files loadPreset(batchPresetName); throw 0 // Set the custom location for exporting setCustomLocation(folderPath, batchPresetName); // Process!! batchProcessorWin.buttons.whoseDescription.is("Process").first.elementClick(); } module.exports = { processBatch };
- In reply toForrester_Savell⬆:
Kitch Membery @Kitch2024-10-11 05:33:59.584Z
- FForrester Savell @Forrester_Savell
Those two scripts worked on my end too.
However, unfortunately it doesn't work (doesn't select the files in the Open window and add the files), when I link your
processBatch
script to my Bounce Masters script. I can email you a link to the video if you think its helpful, but basically stalls at the same point you were seeing on Zoom.There's something going in between some of my test scripts where if I let them go through to the Process part, some won't create the 'Masters 44.1khz_16bit' folder and some will. Need to do more experimenting when I have time, but it may provide more clues. Standby.
- FForrester Savell @Forrester_Savell
Hey @Kitch
Finally got around to tackling this again. It was so silly. The array of filenames I was handing into the iZo batch process script (from the Bounce Masters script) didn't have .wav extensions, so the
openFiles
function wasn't finding them. When we were testing with hard-coded filenames, we had the .wav extension. Hence the Command ID part was always failing.The other issue I alluded to in my last post was the folder creation part was failing a lot, but a
sf.wait
sorted that.So now it's a great bounce and process script, thanks for your help!!
Kitch Membery @Kitch2024-10-24 05:08:37.242Z
Awesome!!!
I think we ironed out a few other things in the process, so not waisted time at all. :-)