No internet connection
  1. Home
  2. Macro and Script Help

iZotope RX Batch Processor Inefficiencies

By Nathan Salefski @nathansalefski
    2023-12-11 19:15:45.052Z2023-12-11 19:30:04.572Z

    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=

    • 16 replies

    There are 16 replies. Estimated reading time: 39 minutes

    1. F
      Forrester Savell @Forrester_Savell
        2024-10-09 23:23:07.938Z2024-10-09 23:30:56.654Z

        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() + 0x7c

        I'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();
        
        1. Kitch Membery @Kitch2024-10-10 01:38:24.686Z2024-10-10 02:22:31.926Z

          Hi @Forrester_Savell,

          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

          1. FForrester Savell @Forrester_Savell
              2024-10-10 22:04:48.032Z

              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');
                  }
              
            • 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");
              
              1. FForrester Savell @Forrester_Savell
                  2024-10-10 02:20:12.767Z

                  Thanks so much for looking into this @Kitch !

                  Can't wait to try these out. I'll report back with the results.

                  1. 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.

                • 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 });
                  }
                  
                  1. FForrester Savell @Forrester_Savell
                      2024-10-11 04:41:33.270Z

                      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.

                      1. Kitch Membery @Kitch2024-10-11 04:56:16.675Z

                        What happens when you remove that line?

                        1. FForrester Savell @Forrester_Savell
                            2024-10-11 05:04:50.089Z

                            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.

                            1. Kitch Membery @Kitch2024-10-11 05:05:39.278Z

                              Cool thanks, back to the drawing board.

                              1. 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 };
                                
                                1. Kitch Membery @Kitch2024-10-11 05:33:59.584Z
                                  1. FForrester Savell @Forrester_Savell
                                      2024-10-11 06:51:01.968Z

                                      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.

                                      1. FForrester Savell @Forrester_Savell
                                          2024-10-24 04:39:03.017Z

                                          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!!

                                          1. 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. :-)