I have a script which clears un-used clips from the clip-list. As a part of this I have a few clicks on the confirmation dialog windows which pop up:
sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
waitType: "Disappear",
timeout: 2000,
});
if(sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists){
//sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick();
//In order to hold Option (to recursively select Yes if the undo queue has multiple entries)
//I need to do a mouseClick rather than an element click
sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
isOption: true,
});
}
This seems to work well when used on it's own (within my script which only clears the un-used clips). However, when used as part of a much larger script I get the following error for this line:
sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
ClickButtonAction requires UIElement (scriptname: Line 228)
Couldn't get item #0 as the array length was 0 - sf.ui.app('com.avid.ProTools')..buttons.whoseTitle.is('Remove').first (AxElementArrayIndexedItem)
If I 'pick' the Remove button I get this:
sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
But if I run just that command nothing happens. Through trial and error I found that in this scenario, only the following will work:
sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Remove").first.elementClick();
So I'm wondering what might be going on here. Any ideas?
Thanks,
-Fergus
- Kitch Membery @Kitch2024-12-21 01:04:06.198Z
Can you share the lines of code prior to the script failing? Preferably a script that recreates the issue in its simplest form.
Hopefully, this information will help me troubleshoot the issue. :-)
Pinewood Studios @Pinewood_Studios
Hi Kitch,
At the moment the script that causes it to fail is very much a WIP, but I've cut it down to a point where it reliably breaks. This is below.
Steps to re-create issue:
-
Start with a Pro Tools session with start time 00:00:00:00
-
Make 6 7.1 audio tracks
-
Render 6 7.1 tone clips (cmd+opt+shift+3) that start at 01:00:00:00 (doesn’t matter their duration)
-
Render 6 more 7.1 clips that start at 02:00:00:00.
-
Delete (backspace) the clips that start at 02:00:00:00
-
Run script below:
// Pro Tools Start Script if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running'; sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //go to Session Start function goToSessionStart() { sf.app.proTools.setTimelineSelection({ inTime: String(Number(0)), outTime: String(Number(0)), }); } //CHANGE SESSION START TO FIRST CLIP IN SESSION goToSessionStart(); // Select all tracks let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name); sf.app.proTools.selectTracksByName({trackNames: ptTracks, selectionMode: "Replace" }); // Select first clip on the timeline and store it's location sf.ui.proTools.clipSelectNextFullClip(); var currentTimelineSelection = sf.app.proTools.getTimelineSelection({timeScale:"Samples"}); sf.app.proTools.setTimelineSelection({ inTime: String(Number(currentTimelineSelection.inTime)), outTime: String(Number(currentTimelineSelection.inTime)), }); currentTimelineSelection = sf.app.proTools.getTimelineSelection({timeScale:"TimeCode"}); //Brings up session setup window if(!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists){ sf.ui.proTools.menuClick({ menuPath: ["Setup","Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType:"Appear", timeout: 500 }); } //If it already is correct do nothing if(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime){ sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick() sf.keyboard.type({ text: currentTimelineSelection.inTime}); //Wait until the correct timecode has been pasted into the field while(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { sf.wait({ intervalMs: 50, }) } sf.keyboard.press({ keys: 'return '}); sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Appear", timeout: 5000 }) sf.keyboard.press({ keys: 'return '}); } //Close the window if(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists){ sf.ui.proTools.menuClick({ menuPath: ["Setup","Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType:"Disappear", timeout: 500 }); } //DESELECT ALL TRACKS sf.app.proTools.selectTracksByName({trackNames: ["THIS WILL DESELECT EVERYTHING"],selectionMode: "Replace"}); //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own? //FUNCTIONS------------------------------------------------------------------------------------------------ //Clear Clips List Filter function clearClipsListFilter() { const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate"); const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first; if (clearClipsFilterButton.exists) { clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`); } } //FUNCTIONS------------------------------------------------------------------------------------------------ sf.ui.proTools.menuClick({ menuPath: ["View","Other Displays","Clip List"], targetValue: "Enable" }); clearClipsListFilter(); //Adjust Clip List Filters (From @Chris_Shaw) const itemsToCheck = { "Audio": true, "MIDI": true, "Video": true, "Groups": true, "Auto-Created": true, "Include subsequently added clips": false, } // Set Clip List filter checkboxes const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first; const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems; const checkBoxes = Object.keys(itemsToCheck) checkBoxes.forEach(cb => { const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0]; const shouldClick = checkBox.isMenuChecked !== itemsToCheck[cb] || typeof itemsToCheck[cb] !== "boolean" if (shouldClick) checkBox.elementClick() }) //Clears up to a maximum of 15 times for (var i=0; i<15; i++){ sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({ menuPath: ["Select","Unused"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({ menuPath: ["Clear..."], }) sf.wait({ intervalMs: 300 }); if(!sf.ui.proTools.confirmationButtonDialog.exists){ //log("Nothing to do") sf.ui.proTools.appActivateMainWindow() break; }; //Wait for window /* sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Appear", timeout: 500, }); */ sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick(); sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Disappear", timeout: 2000, }); if(sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists){ //sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick(); //In order to hold Option (to recursively select Yes if the undo queue has multiple entries) //I need to do a mouseClick rather than an elementClick sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({ isOption: true, }); } }
Thanks in advance for your help.
--Fergus
Chad Wahlbrink @Chad2024-12-21 17:04:33.614Z
Thanks, @Pinewood_Studios,
I believe this is related to a bug in which Pro Tools creates temporary windows with null values discussed a bit here:
A Better Temp Group System #post-3There may be a bug with addressing this specific confirmation dialog that I'll log after I post this.
Regardless, a fix seems to be to use our handy friend
invalidate()
.If you use:
sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementClick();
Instead of
sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
It should work as expected.
I also found that using this line to address the popup menu for the clip list in your script was failing for me:
sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({ menuPath: ["Select","Unused"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({ menuPath: ["Clear..."], })
So I would address it like this instead, to be a bit more explicit:
sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Select", "Unused"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Clear..."], })
Finally, I also found that doing a quick
sf.ui.proTools.appActivateMainWindow();
call at about line 131 (just after you set the checkboxes for the cliplist filter), refreshed the UI a bit for that next step of using the "Clip List" popupmenu.
The full updated script would be this:
// Pro Tools Start Script if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running'; sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //go to Session Start function goToSessionStart() { sf.app.proTools.setTimelineSelection({ inTime: String(Number(0)), outTime: String(Number(0)), }); } //CHANGE SESSION START TO FIRST CLIP IN SESSION goToSessionStart(); // Select all tracks let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name); sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" }); // Select first clip on the timeline and store it's location sf.ui.proTools.clipSelectNextFullClip(); var currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); sf.app.proTools.setTimelineSelection({ inTime: String(Number(currentTimelineSelection.inTime)), outTime: String(Number(currentTimelineSelection.inTime)), }); currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" }); //Brings up session setup window if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.menuClick({ menuPath: ["Setup", "Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType: "Appear", timeout: 500 }); } //If it already is correct do nothing if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick() sf.keyboard.type({ text: currentTimelineSelection.inTime }); //Wait until the correct timecode has been pasted into the field while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { sf.wait({ intervalMs: 50, }) } sf.keyboard.press({ keys: 'return ' }); sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Appear", timeout: 5000 }) sf.keyboard.press({ keys: 'return ' }); } //Close the window if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.menuClick({ menuPath: ["Setup", "Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType: "Disappear", timeout: 500 }); } //DESELECT ALL TRACKS sf.app.proTools.selectTracksByName({ trackNames: ["THIS WILL DESELECT EVERYTHING"], selectionMode: "Replace" }); //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own? //FUNCTIONS------------------------------------------------------------------------------------------------ //Clear Clips List Filter function clearClipsListFilter() { const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate"); const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first; if (clearClipsFilterButton.exists) { clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`); } } //FUNCTIONS------------------------------------------------------------------------------------------------ sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); clearClipsListFilter(); //Adjust Clip List Filters (From @Chris_Shaw) const itemsToCheck = { "Audio": true, "MIDI": true, "Video": true, "Groups": true, "Auto-Created": true, "Include subsequently added clips": false, } // Set Clip List filter checkboxes const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first; const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems; const checkBoxes = Object.keys(itemsToCheck) checkBoxes.forEach(cb => { const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0]; const shouldClick = checkBox.isMenuChecked !== itemsToCheck[cb] || typeof itemsToCheck[cb] !== "boolean" if (shouldClick) checkBox.elementClick() }) sf.ui.proTools.appActivateMainWindow(); //Clears up to a maximum of 15 times for (var i = 0; i < 15; i++) { sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Select", "Unused"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Clear..."], }) sf.wait({ intervalMs: 300 }); if (!sf.ui.proTools.confirmationButtonDialog.exists) { //log("Nothing to do") sf.ui.proTools.appActivateMainWindow() break; }; //Wait for window /* sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Appear", timeout: 500, }); */ sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementClick(); sf.ui.proTools.confirmationButtonDialog.elementWaitFor({ waitType: "Disappear", timeout: 2000, }); if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) { //sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick(); //In order to hold Option (to recursively select Yes if the undo queue has multiple entries) //I need to do a mouseClick rather than an elementClick sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({ isOption: true, }); } }
Chad Wahlbrink @Chad2024-12-21 17:05:40.545Z
Finally, here's a version that's a bit cleaner and optimized.
I'm not doing a for loop at the end as I wasn't sure of what that was trying to accomplish.
// Pro Tools Start Script if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running'; sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //FUNCTIONS------------------------------------------------------------------------------------------------ //Clear Clips List Filter function clearClipsListFilter() { const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate"); const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first; if (clearClipsFilterButton.exists) { clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`); } } // Go to Session Start function goToSessionStart() { sf.app.proTools.setTimelineSelection({ inTime: String(Number(0)), outTime: String(Number(0)), }); } //------------------------------------------------------------------------------------------------ function main() { // CHANGE SESSION START TO FIRST CLIP IN SESSION goToSessionStart(); // Select all tracks let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name); sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" }); // Select first clip on the timeline and store it's location sf.ui.proTools.clipSelectNextFullClip(); let currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); // Go to the start of the first clip sf.app.proTools.setTimelineSelection({ inTime: String(Number(currentTimelineSelection.inTime)), outTime: String(Number(currentTimelineSelection.inTime)), }); // Get Current Timeline Selection as Timecode currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" }); // Open Session Setup if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.menuClick({ menuPath: ["Setup", "Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor(); } // Set the Session Start time to the timecode position of the first clip. If it's already set, then move on. if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { // Select the text field for "Session Start" sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick() // We have to simulate typing for the "Session Start" field of the session setup window sf.keyboard.type({ text: currentTimelineSelection.inTime }); //Wait until the correct timecode has been typed into the field while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { sf.wait({ intervalMs: 50, }) } // Press Return to Complete Entering the Timecode in the Session Start Field sf.keyboard.press({ keys: 'return ' }); // Wait for the confirmation dialog button sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementWaitFor(); // Click for the confirmation dialog button sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementClick(); } // If the Session Setup window is open, close the window if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.getElement("AXCloseButton").elementClick(); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType: "Disappear", }); } // DESELECT ALL TRACKS sf.app.proTools.selectTracksByName({ trackNames: [], selectionMode: "Replace" }); // Make sure the clip list is shown sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own? clearClipsListFilter(); //Adjust Clip List Filters (From @Chris_Shaw) // Create an Object of items to for the clip list filter to include const itemsToCheck = { "Audio": true, "MIDI": true, "Video": true, "Groups": true, "Auto-Created": true, "Include subsequently added clips": false, } // Set Clip List filter checkboxes const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first; const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems; const checkBoxes = Object.keys(itemsToCheck) checkBoxes.forEach(cb => { const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0]; const shouldClick = checkBox.isMenuChecked !== itemsToCheck[cb] || typeof itemsToCheck[cb] !== "boolean" if (shouldClick) checkBox.elementClick(); }) // Quick Little UI refresh sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Select", "Unused"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Clear..."], }) if (!sf.ui.proTools.confirmationButtonDialog.exists) { // Skip if No Dialogs are Present sf.ui.proTools.appActivateMainWindow(); } else { // We need to invalidate the confirmationButtonDialog cache // Also, We can wait for an element and then click it in one line. sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementWaitFor().element.elementClick(); // Clear All if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({ isOption: true, }); } // This is a simple way we can wait for all dialogs to be gone sf.ui.proTools.waitForNoModals(); } } main();
Pinewood Studios @Pinewood_Studios
Thanks as ever Chad, for the detailed explanation and script optimisation. I'll give it a whirl tomorrow.
The for loop in my script is because you quite often have to perform the 'select unused + clear clips' process multiple times to get them all (we're very used to spamming cmd+shift+u, cmd+shift+b, repeatedly until you get no further pop-ups).
Specifically, this happens if you delete a clip group which contains other clips, the first time you select un-used you only get the clip group. If you run the same thing again you get the next 'layer' and so on and so on..
I figured a maximum of 15 times is a fairly safe bet, and you can see in my original script I used a quick wait to see if no dialogs popped up, at which point I assume there are no clips left to clear, and I break the loop. As I said.. just a first draft! :)
And if I may ask - as a new-user I'm intrigued how you are able to find the more specific names of the popupButtons. If I 'pick' them I don't seem to get the detail I need (e.g. "Show Options menu" or "Show Filter Options"). I would love to know how that is figured out!
Thanks again,
Fergus.Chad Wahlbrink @Chad2024-12-22 13:41:32.403Z
Here's a quick video on how I'm grabbing this alternate UI path:
In short, using a macro action "Click UI Element" and the pick action allows you to formulate some alternate paths and then copy that action as javascript:
- In reply toChad⬆:
Pinewood Studios @Pinewood_Studios
Hey Chad,
A quick follow-up. Interestingly, for me your:
sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({ menuPath: ["Clear..."], })
doesn't work - I get "Could not open popup menu, popupMenu.open.fromElement requires an element". Perhaps due to a Pro Tools version mismatch. We're on PT 2024.6 at the moment.
Fergus
Chad Wahlbrink @Chad2024-12-22 13:38:52.479Z
Ah, yes. The Clip List ui went through some changes on Pro Tools 2024.10 when they made it detachable.
We can address the UI differently for different versions with this bit of code:
const ptVersion = sf.ui.proTools.appVersionAsNumber; let clipListPopupMenu; if (ptVersion < 241000) { clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Clip List").first; } else { clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first; }
This version of the script should work on both 2024.6 and 2024.10 and includes the for loop for extra clearing:
// Pro Tools Start Script if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running'; sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); const ptVersion = sf.ui.proTools.appVersionAsNumber; let clipListPopupMenu; if (ptVersion < 241000) { clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Clip List").first; } else { clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first; } //FUNCTIONS------------------------------------------------------------------------------------------------ //Clear Clips List Filter function clearClipsListFilter() { const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate"); const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first; if (clearClipsFilterButton.exists) { clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`); } } // Go to Session Start function goToSessionStart() { sf.app.proTools.setTimelineSelection({ inTime: String(Number(0)), outTime: String(Number(0)), }); } //------------------------------------------------------------------------------------------------ function main() { // CHANGE SESSION START TO FIRST CLIP IN SESSION goToSessionStart(); // Select all tracks let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name); sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" }); // Select first clip on the timeline and store it's location sf.ui.proTools.clipSelectNextFullClip(); let currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); // Go to the start of the first clip sf.app.proTools.setTimelineSelection({ inTime: String(Number(currentTimelineSelection.inTime)), outTime: String(Number(currentTimelineSelection.inTime)), }); // Get Current Timeline Selection as Timecode currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" }); // Open Session Setup if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.menuClick({ menuPath: ["Setup", "Session"], }); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor(); } // Set the Session Start time to the timecode position of the first clip. If it's already set, then move on. if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { // Select the text field for "Session Start" sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick() // We have to simulate typing for the "Session Start" field of the session setup window sf.keyboard.type({ text: currentTimelineSelection.inTime }); //Wait until the correct timecode has been typed into the field while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) { sf.wait({ intervalMs: 50, }) } // Press Return to Complete Entering the Timecode in the Session Start Field sf.keyboard.press({ keys: 'return ' }); // Wait for the confirmation dialog button sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementWaitFor(); // Click for the confirmation dialog button sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementClick(); } // If the Session Setup window is open, close the window if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) { sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.getElement("AXCloseButton").elementClick(); sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({ waitType: "Disappear", }); } // DESELECT ALL TRACKS sf.app.proTools.selectTracksByName({ trackNames: [], selectionMode: "Replace" }); // Make sure the clip list is shown sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own? clearClipsListFilter(); //Adjust Clip List Filters (From @Chris_Shaw) // Create an Object of items to for the clip list filter to include const itemsToCheck = { "Audio": true, "MIDI": true, "Video": true, "Groups": true, "Auto-Created": true, "Include subsequently added clips": false, } // Set Clip List filter checkboxes const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first; const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems; const checkBoxes = Object.keys(itemsToCheck) checkBoxes.forEach(cb => { const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0]; const shouldClick = checkBox.isMenuChecked !== itemsToCheck[cb] || typeof itemsToCheck[cb] !== "boolean" if (shouldClick) checkBox.elementClick(); }) // Quick Little UI refresh sf.ui.proTools.appActivateMainWindow(); //Clears up to a maximum of 15 times for (var i = 0; i < 15; i++) { clipListPopupMenu.popupMenuSelect({ menuPath: ["Select", "Unused"], targetValue: "Enable" }); clipListPopupMenu.popupMenuSelect({ menuPath: ["Clear..."], }) sf.wait({ intervalMs: 300 }); if (!sf.ui.proTools.confirmationButtonDialog.exists) { //log("Nothing to do") sf.ui.proTools.appActivateMainWindow() break; } else { // We need to invalidate the confirmationButtonDialog cache // Also, We can wait for an element and then click it in one line. sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementWaitFor().element.elementClick(); // Clear All if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({ isOption: true, }); } // This is a simple way we can wait for all dialogs to be gone sf.ui.proTools.waitForNoModals(); } } } main();
Pinewood Studios @Pinewood_Studios
Great stuff. I don't seem to have the appVersionAsNumber option, but the logic is sound. Thanks mate!
Kitch Membery @Kitch2024-12-23 21:34:56.518Z
The
appVersionAsNumber
property was introduced in SoundFlow 5.10.1. Be sure to install the latest version of SoundFlow from https://soundflow.org/account/install:-)
Chad Wahlbrink @Chad2024-12-23 22:45:19.729Z
Thanks @Kitch!
Yes, that is some new code for us.
In times past, we would do this:
const ptVersion = sf.ui.proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0); if (ptVersion < 230900) { } else { }
-
- SIn reply toPinewood_Studios⬆:SoundFlow Bot @soundflowbot
This report was now added to the internal issue tracked by SoundFlow as SF-1546