Hi!
I'm making a script to remove all inactive tracks in a session, delete unused playlists and clear all unused audio files.
The latter parts I can find looking in the forum, but selecting and removing all inactive tracks seems to be really hard.
I've come across @Kitch 's script for loging tracks states, which seems to be a good beginning, but I can't seem to go from there. Any help would be super appreciated!
function isTrackEnabled(trackName) {
const track = sf.ui.proTools.trackGetByName({ name: trackName }).track
const isInactive = track.buttons.whoseTitle.is("Mute").first.value.value.startsWith('inactive')
return {
trackName: trackName,
isInactive: isInactive
}
}
function main() {
sf.ui.proTools.appActivateMainWindow();
const selectedTrackNames = sf.ui.proTools.selectedTrackNames
let trackActiveStates = [];
selectedTrackNames.forEach(trackName => {
let result = isTrackEnabled(trackName);
log(result)
trackActiveStates.push(result);
});
log(trackActiveStates);
}
main();
Linked from:
- Kitch Membery @Kitch2021-10-13 20:18:20.698Z
Hi @Gabriel_Lundh,
This will selected inactive audio tracks from a selection. But will not work on tracks without "Mute" buttons (ie Master, VCA, MIDI, Basic Folder etc). I've left out the delete portion of the script... I'll let you ad that :-)
function isTrackActive(trackName) { const track = sf.ui.proTools.trackGetByName({ name: trackName }).track const muteBtn = track.buttons.whoseTitle.is("Mute").first; let isInactive = muteBtn.exists ? track.buttons.whoseTitle.is("Mute").first.value.value.startsWith('inactive') : false; return { trackName: trackName, isInactive: isInactive } } function main() { sf.ui.proTools.appActivateMainWindow(); const selectedTrackNames = sf.ui.proTools.selectedTrackNames; const trackActiveStates = selectedTrackNames.map(trackName => isTrackActive(trackName)); const inactiveTrackNames = trackActiveStates.filter(track => track.isInactive).map(track => track.trackName); sf.ui.proTools.trackSelectByName({ names: inactiveTrackNames }); } main();
You may want to take a different approach and use the "Track List Pop-up" to show only "Inactive Tracks like this
And then after selecting and deleting the tracks, click "Restore Previously Shown Tracks".
I hope that helps
Rock on!
PS Great to see you in the webinar today :-)
- GGabriel Lundh @Gabriel_Lundh
Hi Kitch!
So fun to see you yesterday, and great hearing a bit about your background!
Thanks for this, super helpful! This script helps me handling with tracks in folders, as it deselects routing folders which have inactive tracks in them, so that they are not deleted.
So big thanks for this, I'll post the script once It's done.All the best,
GabrielKitch Membery @Kitch2021-10-14 17:12:05.429Z
Great to meet you too legend!!! Glad I could help :-)
- In reply toKitch⬆:GGabriel Lundh @Gabriel_Lundh
Hi @Kitch !
I just wanted to say that I finally figured the whole script out. There came up some serious road bumps while doing the master script, but I managed to get around them by stealing code from around the brilliant forum!
I take no credit for the functions, everything is made from all you geniuses!!
I'll share it here!function saveSessionAsNewVersion() { //First save the current version sf.ui.proTools.getMenuItem('File', 'Save').elementClick({}, function () { }); //can be disabled //Get our filename without extension var fileNameWithoutExtension = sf.ui.proTools.mainWindow.invalidate().sessionPath.split('/').slice(-1)[0].split('.').slice(0, -1).join('.'); //Create a new name (get the name and suffixed number - if your session name was 'My Session 42' then save 'My Session ' in grps[1] and '42' in grps[2]) var grps = fileNameWithoutExtension.match(/^(.+?)(\d+)$/); //Construct a new name based on the prefix and the number (+1), followed by the '.ptx' suffix. If there was no number, just add ' 2' var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? '_2' : ((Number(grps[2]) + 1) + ''); var newName = (grps ? grps[1] : (fileNameWithoutExtension)) + newNumber + '.ptx'; //Invoke File Save As. sf.ui.proTools.getMenuItem('File', 'Save As...').elementClick(); //Wait for the Save dialog to appear var dlg = sf.ui.proTools.windows.invalidate().whoseTitle.is('Save').first.elementWaitFor().element; //Enter the new name dlg.textFields.first.value.value = newName; //Click Save dlg.buttons.whoseTitle.is('Save').first.elementClick(); } function selectAllFolderTracks() { var visibleTracks = sf.ui.proTools.trackGetVisibleTracks().names var firstTrackFound; let foundTracks = []; while (true) { sf.ui.proTools.trackDeselectAll(); for (var i = 0; i < visibleTracks.length; i++) { var trackIsFolder = sf.ui.proTools.trackGetByName({ name: visibleTracks[i] }).track.children.whoseRole.is("AXDisclosureTriangle").whoseTitle.is('Open Folder') if (trackIsFolder.exists) if (trackIsFolder) { if (!firstTrackFound) { // Scroll to first track found sf.ui.proTools.trackGetByName({ name: visibleTracks[i], }).track.trackScrollToView(); firstTrackFound = !firstTrackFound; } else { sf.ui.proTools.trackSelectByName({ names: [visibleTracks[i]], deselectOthers: false }); } foundTracks.push(visibleTracks[i]); } } if (foundTracks.length !== 0) { break; } else if (foundTracks.length == 0) { return "exit"; } } } function openFolderTracks() { sf.ui.proTools.appActivateMainWindow(); while (true) { var temp; temp = selectAllFolderTracks(); if (temp == "exit"){ return 0; } const selectedTracks = sf.ui.proTools.selectedTrackNames if (selectedTracks.length >= 1) { if (selectedTracks.length == 1) { sf.ui.proTools.selectedTrack.folderTrackSetOpen({ targetValue: 'Enable', onError: 'Continue' }); } if (selectedTracks.length >= 2) { for (var i = 0; i < selectedTracks.length; i++) { sf.ui.proTools.invalidate().trackGetByName({ name: selectedTracks[i] }).track.folderTrackSetOpen({ targetValue: 'Enable', }); } } } } } function setGroupActive(groupName, targetValue) { let grp = sf.ui.proTools.mainWindow.groupListView.childrenByRole('AXRow').map(r => { let btn = r.childrenByRole('AXCell').allItems[1].buttons.first; return { groupName: r.childrenByRole('AXCell').allItems[1].buttons.first.title.value.split(/-\s/)[1], btn, isActive: btn.title.invalidate().value.indexOf('Active ') === 0, row: r, }; }).filter(g => g.groupName === groupName)[0]; if (!grp) throw `Group with name "${groupName}" was not found`; if (grp.isActive !== !!targetValue) grp.btn.elementClick(); } function isTrackActive(trackName) { const track = sf.ui.proTools.trackGetByName({ name: trackName }).track const muteBtn = track.buttons.whoseTitle.is("Mute").first; let isInactive = muteBtn.exists ? track.buttons.whoseTitle.is("Mute").first.value.value.startsWith('inactive') : false; return { trackName: trackName, isInactive: isInactive } } function selectInactiveTracks() { sf.ui.proTools.appActivateMainWindow(); const selectedTrackNames = sf.ui.proTools.selectedTrackNames; const trackActiveStates = selectedTrackNames.map(trackName => isTrackActive(trackName)); const inactiveTrackNames = trackActiveStates.filter(track => track.isInactive).map(track => track.trackName); sf.ui.proTools.trackSelectByName({ names: inactiveTrackNames }); } function dismissDialog(dialogText, buttonName) { const dlg = sf.ui.proTools.confirmationDialog; //Wait 100ms for dialog box to appear dlg.elementWaitFor({ timeout: 100, pollingInterval: 10, onError: 'Continue' }); if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) { dlg.buttons.whoseTitle.is(buttonName).first.elementClick(); dlg.elementWaitFor({ waitType: 'Disappear', timeout: 100, pollingInterval: 10, onError: 'Continue' }); } } function dismissDialog2(dialogText, buttonName) { const dlg = sf.ui.proTools.confirmationDialog; //Wait 100ms for dialog box to appear dlg.elementWaitFor({ timeout: 100, pollingInterval: 10, onError: 'Continue' }); if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) { dlg.buttons.whoseTitle.is(buttonName).first.elementClick(); dlg.elementWaitFor({ waitType: 'Disappear', timeout: 100, pollingInterval: 10, onError: 'Continue' }); } } function dismissDialog3(dialogText, buttonName) { const dlg = sf.ui.proTools.confirmationDialog; //Wait 100ms for dialog box to appear dlg.elementWaitFor({ timeout: 100, pollingInterval: 10, onError: 'Continue' }); if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) { dlg.buttons.whoseTitle.is(buttonName).first.elementClick(); dlg.elementWaitFor({ waitType: 'Disappear', timeout: 100, pollingInterval: 10, onError: 'Continue' }); } } function selectAllTracks() { const groupsList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const firstRow = groupsList.childrenByRole("AXRow"); const firstCell = firstRow.first.childrenByRole("AXCell").first.buttons.first; firstCell.elementClick(); } function goToStartOfSession() { sf.ui.proTools.transportEnsureCluster(); const trannsportCluster = sf.ui.proTools.mainWindow.transportViewCluster; const transportButtons = trannsportCluster.groups.whoseTitle.is("Normal Transport Buttons").first; transportButtons.buttons.whoseTitle.is("Return to Zero").first.elementClick(); } function setWindowView() { sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Track List"], targetValue: "Enable" }); sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Track List pop-up").first.popupMenuSelect({ menuPath: ["Show All Tracks"], }); function showItems() { function show(item) { sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Show", item], targetValue: "Enable" }); } show("Audio") show("MIDI") show("Video") show("Groups") show("Auto-Created") } showItems() } /** * @param {string} trackType - All track types included on trackTypes array */ function deleteUnusedPlaylists(trackType) { // Check if there are any tracks of trackType const trackTypeExists = sf.ui.proTools.visibleTrackHeaders.some(x => x.title.invalidate().value.includes(trackType)) if (trackTypeExists) { // Get name of first track of trackType const firstTrackByTypeName = sf.ui.proTools.visibleTrackHeaders.filter(x => x.title.invalidate().value.includes(trackType))[0].normalizedTrackName // Find track by name const getFirstTrackByTypeName = sf.ui.proTools.trackGetByName({ name: firstTrackByTypeName }).track // Scrool track if needed getFirstTrackByTypeName.trackScrollToView() // Delete unused menu getFirstTrackByTypeName.popupButtons.whoseTitle.is("Playlist selector").first.popupMenuSelect({ menuPath: ["Delete Unused..."] }); // Delete unused window const deleteUnusedWin = sf.ui.proTools.windows.whoseTitle.is("Delete Unused Playlists").first if (deleteUnusedWin.exists) { const deleteUnusedWinTable = deleteUnusedWin.tables.first.children.whoseRole.is("AXRow") // Select all unused playlists deleteUnusedWinTable.map(x => x.children.first.children.whoseRole.is("AXStaticText").first.elementClick()) // Click delete Btn deleteUnusedWin.buttons.whoseTitle.is("Delete").first.elementClick(); }; }; }; function deletePlaylistesMain() { sf.ui.proTools.appActivateMainWindow(); // show Track list, Clip list and All tracks setWindowView() // Clear all items from clip list // delete unused playlists, needs to be done on all track types const trackTypes = [ "Video Track", "MIDI Track", "Inst Track", "Audio Track" ] trackTypes.forEach(deleteUnusedPlaylists) } function saveAndClose() { while (true) { //// wait for complete tasks if (sf.ui.proTools.windows.whoseTitle.is('Task Manager').first.children.whoseRole.is("AXStaticText").first.invalidate().value.value == "0 items") //// save and exit procedures if ((sf.ui.proTools.mainWindow.invalidate().sessionPath !== null || sf.ui.proTools.getMenuItem('File', 'Close Session').isEnabled) || sf.ui.proTools.confirmationDialog.exists) { sf.ui.proTools.waitForNoModals(); sf.ui.proTools.menuClick({ menuPath: ["File", "Save"] }); sf.ui.proTools.menuClick({ menuPath: ["File", "Close Session"] }); if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Save').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Save').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Cancel').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Cancel').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } sf.ui.proTools.waitForNoModals(); } else { break } sf.wait({ intervalMs: 500 }) } } function waitForOpenSession(m1, m2) { while (!sf.ui.proTools.getMenuItem(m1, m2).isEnabled) { // Reset menu sf.keyboard.press({ keys: "left" }); sf.wait({ intervalMs: 100 }); } } function saveCloseAndOpenAgain (){ saveAndClose() sf.ui.proTools.mainWindow.elementWaitFor({ waitType: "Disappear", timeout: -1 }); sf.file.open({ path: sessionDir }); // Wait fo menus to become enabled, thus sessioon is fully opened waitForOpenSession('File', 'Save'); } saveSessionAsNewVersion(); sf.ui.proTools.appActivateMainWindow openFolderTracks(); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show Only", "Inactive Tracks"], }); setGroupActive("<ALL>", true); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Edit Keyboard Focus").first.mouseClickElement({ relativePosition: {"x":-10,"y":7}, anchor: "BottomLeft", }); setGroupActive("<ALL>", false); selectInactiveTracks(); sf.ui.proTools.trackDelete(); dismissDialog("Active clips were found","Delete"); dismissDialog2("The track","Delete"); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show All Tracks"], }); deletePlaylistesMain() sf.ui.proTools.menuClick({ menuPath: ["View","Other Displays","Clip List"], targetValue: "Enable", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Select","\"Select Unused\" Ignores Parent Clip"], targetValue: "Disable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog3("REMOVE","Remove"); /// Need task manager to close sessions if (!sf.ui.proTools.getMenuItem("Window", "Task Manager").isMenuChecked) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Task Manager"], targetValue: "Enable" }); } var sessionDir = sf.ui.proTools.mainWindow.sessionPath saveCloseAndOpenAgain(); sf.ui.proTools.menuClick({ menuPath: ["View","Other Displays","Clip List"], targetValue: "Enable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog3("REMOVE","Remove"); sf.keyboard.press({ keys: "left" }); sf.ui.proTools.menuClick({ menuPath: ["Window","Task Manager"], targetValue: "Disable", });
Kitch Membery @Kitch2021-10-14 17:18:27.506Z
Wow! Epic! I’ll have a closer look at it later.
Nice work!
- In reply toGabriel_Lundh⬆:Brett Ryan Stewart @Brett_Ryan_Stewart
@Kitch were you able to get this to work? didn't work on my system but would LOVE this option. Thanks for building it @Gabriel_Lundh
Kitch Membery @Kitch2022-01-11 08:00:42.501Z
Which script are you trying to run? Were you seeing an error message when running the script?
Rock on!
- GGabriel Lundh @Gabriel_Lundh
Hi @Brett_Ryan_Stewart !
Please let us know - the script is working here - BUT - I still have to put the time in and figure out all the possible Dialog boxes that could potentially pop up.
If you've for example just removed a clip, that is in your undo que, the script fails as I've not put in a dismiss Dialog for that yet. I just press OK and run the script again if it fails - as it's rather quick :)@Kitch, do you have any idea on how to dismiss all potential dialogs that could pop up during this script? I've been using your script for this as you can see - but it is a lot of potential and trying to figure out the right order of these potential dialogs that makes this script so hard to make bullet proof.
Thanks!
All the best,
GabrielBrett Ryan Stewart @Brett_Ryan_Stewart
@Gabriel_Lundh @Kitch strangely today it worked! Very cool! It did pop up an error if this is helpful...
Could not click popup menu item (Select and Delete Inactive Tracks: Line 219)That said, there weren't any leftover popups on the screen.
- GGabriel Lundh @Gabriel_Lundh
Perfect @Brett_Ryan_Stewart ! Interesting that that failed - could have been something that messed with the clip list. I'll try and make this more bullet proof in the coming days! Let's see if the brilliant @Kitch has any idea of making a generic "close all potential pop up dialogs" option.
All the best,
GabrielBrett Ryan Stewart @Brett_Ryan_Stewart
Dig that man! Hey is this going to be in the store? JW because I'll wanna to stay abreast to updates :-)
- In reply toGabriel_Lundh⬆:
Kitch Membery @Kitch2022-01-12 19:51:20.922Z
You're too kind :-)
I do have an idea on how to attempt this but I won't be able to get around to it for the next couple of weeks. Bump me in this thread in a couple of weeks and I'll see what I can do :-)
Rock on!
- In reply toGabriel_Lundh⬆:
Kitch Membery @Kitch2022-01-12 20:08:33.393Z2022-01-12 22:21:23.077Z
One thing I'm noticing in your script is that you've duplicated the
dismissDialog
function three times iedismissDialog
,dismissDialog2
,dismissDialog3
. You only need the first function;Here is a super quick refactor to remove them. I also created a
main
function so the script is a little easier to read.function saveSessionAsNewVersion() { //First save the current version sf.ui.proTools.getMenuItem('File', 'Save').elementClick({}, function () { }); //can be disabled //Get our filename without extension var fileNameWithoutExtension = sf.ui.proTools.mainWindow.invalidate().sessionPath.split('/').slice(-1)[0].split('.').slice(0, -1).join('.'); //Create a new name (get the name and suffixed number - if your session name was 'My Session 42' then save 'My Session ' in grps[1] and '42' in grps[2]) var grps = fileNameWithoutExtension.match(/^(.+?)(\d+)$/); //Construct a new name based on the prefix and the number (+1), followed by the '.ptx' suffix. If there was no number, just add ' 2' var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? '_2' : ((Number(grps[2]) + 1) + ''); var newName = (grps ? grps[1] : (fileNameWithoutExtension)) + newNumber + '.ptx'; //Invoke File Save As. sf.ui.proTools.getMenuItem('File', 'Save As...').elementClick(); //Wait for the Save dialog to appear var dlg = sf.ui.proTools.windows.invalidate().whoseTitle.is('Save').first.elementWaitFor().element; //Enter the new name dlg.textFields.first.value.value = newName; //Click Save dlg.buttons.whoseTitle.is('Save').first.elementClick(); } function selectAllFolderTracks() { var visibleTracks = sf.ui.proTools.trackGetVisibleTracks().names var firstTrackFound; let foundTracks = []; while (true) { sf.ui.proTools.trackDeselectAll(); for (var i = 0; i < visibleTracks.length; i++) { var trackIsFolder = sf.ui.proTools.trackGetByName({ name: visibleTracks[i] }).track.children.whoseRole.is("AXDisclosureTriangle").whoseTitle.is('Open Folder') if (trackIsFolder.exists) if (trackIsFolder) { if (!firstTrackFound) { // Scroll to first track found sf.ui.proTools.trackGetByName({ name: visibleTracks[i], }).track.trackScrollToView(); firstTrackFound = !firstTrackFound; } else { sf.ui.proTools.trackSelectByName({ names: [visibleTracks[i]], deselectOthers: false }); } foundTracks.push(visibleTracks[i]); } } if (foundTracks.length !== 0) { break; } else if (foundTracks.length == 0) { return "exit"; } } } function openFolderTracks() { sf.ui.proTools.appActivateMainWindow(); while (true) { var temp; temp = selectAllFolderTracks(); if (temp == "exit") { return 0; } const selectedTracks = sf.ui.proTools.selectedTrackNames if (selectedTracks.length >= 1) { if (selectedTracks.length == 1) { sf.ui.proTools.selectedTrack.folderTrackSetOpen({ targetValue: 'Enable', onError: 'Continue' }); } if (selectedTracks.length >= 2) { for (var i = 0; i < selectedTracks.length; i++) { sf.ui.proTools.invalidate().trackGetByName({ name: selectedTracks[i] }).track.folderTrackSetOpen({ targetValue: 'Enable', }); } } } } } function setGroupActive(groupName, targetValue) { let grp = sf.ui.proTools.mainWindow.groupListView.childrenByRole('AXRow').map(r => { let btn = r.childrenByRole('AXCell').allItems[1].buttons.first; return { groupName: r.childrenByRole('AXCell').allItems[1].buttons.first.title.value.split(/-\s/)[1], btn, isActive: btn.title.invalidate().value.indexOf('Active ') === 0, row: r, }; }).filter(g => g.groupName === groupName)[0]; if (!grp) throw `Group with name "${groupName}" was not found`; if (grp.isActive !== !!targetValue) grp.btn.elementClick(); } function isTrackActive(trackName) { const track = sf.ui.proTools.trackGetByName({ name: trackName }).track const muteBtn = track.buttons.whoseTitle.is("Mute").first; let isInactive = muteBtn.exists ? track.buttons.whoseTitle.is("Mute").first.value.value.startsWith('inactive') : false; return { trackName: trackName, isInactive: isInactive } } function selectInactiveTracks() { sf.ui.proTools.appActivateMainWindow(); const selectedTrackNames = sf.ui.proTools.selectedTrackNames; const trackActiveStates = selectedTrackNames.map(trackName => isTrackActive(trackName)); const inactiveTrackNames = trackActiveStates.filter(track => track.isInactive).map(track => track.trackName); sf.ui.proTools.trackSelectByName({ names: inactiveTrackNames }); } function dismissDialog(dialogText, buttonName) { const dlg = sf.ui.proTools.confirmationDialog; //Wait 100ms for dialog box to appear dlg.elementWaitFor({ timeout: 100, pollingInterval: 10, onError: 'Continue' }); if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) { dlg.buttons.whoseTitle.is(buttonName).first.elementClick(); dlg.elementWaitFor({ waitType: 'Disappear', timeout: 100, pollingInterval: 10, onError: 'Continue' }); } } function selectAllTracks() { const groupsList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const firstRow = groupsList.childrenByRole("AXRow"); const firstCell = firstRow.first.childrenByRole("AXCell").first.buttons.first; firstCell.elementClick(); } function goToStartOfSession() { sf.ui.proTools.transportEnsureCluster(); const trannsportCluster = sf.ui.proTools.mainWindow.transportViewCluster; const transportButtons = trannsportCluster.groups.whoseTitle.is("Normal Transport Buttons").first; transportButtons.buttons.whoseTitle.is("Return to Zero").first.elementClick(); } function setWindowView() { sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Track List"], targetValue: "Enable" }); sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Track List pop-up").first.popupMenuSelect({ menuPath: ["Show All Tracks"], }); function showItems() { function show(item) { sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Show", item], targetValue: "Enable" }); } show("Audio") show("MIDI") show("Video") show("Groups") show("Auto-Created") } showItems() } /** * @param {string} trackType - All track types included on trackTypes array */ function deleteUnusedPlaylists(trackType) { // Check if there are any tracks of trackType const trackTypeExists = sf.ui.proTools.visibleTrackHeaders.some(x => x.title.invalidate().value.includes(trackType)) if (trackTypeExists) { // Get name of first track of trackType const firstTrackByTypeName = sf.ui.proTools.visibleTrackHeaders.filter(x => x.title.invalidate().value.includes(trackType))[0].normalizedTrackName // Find track by name const getFirstTrackByTypeName = sf.ui.proTools.trackGetByName({ name: firstTrackByTypeName }).track // Scrool track if needed getFirstTrackByTypeName.trackScrollToView() // Delete unused menu getFirstTrackByTypeName.popupButtons.whoseTitle.is("Playlist selector").first.popupMenuSelect({ menuPath: ["Delete Unused..."] }); // Delete unused window const deleteUnusedWin = sf.ui.proTools.windows.whoseTitle.is("Delete Unused Playlists").first if (deleteUnusedWin.exists) { const deleteUnusedWinTable = deleteUnusedWin.tables.first.children.whoseRole.is("AXRow") // Select all unused playlists deleteUnusedWinTable.map(x => x.children.first.children.whoseRole.is("AXStaticText").first.elementClick()) // Click delete Btn deleteUnusedWin.buttons.whoseTitle.is("Delete").first.elementClick(); }; }; }; function deletePlaylistesMain() { sf.ui.proTools.appActivateMainWindow(); // show Track list, Clip list and All tracks setWindowView() // Clear all items from clip list // delete unused playlists, needs to be done on all track types const trackTypes = [ "Video Track", "MIDI Track", "Inst Track", "Audio Track" ] trackTypes.forEach(deleteUnusedPlaylists) } function saveAndClose() { while (true) { //// wait for complete tasks if (sf.ui.proTools.windows.whoseTitle.is('Task Manager').first.children.whoseRole.is("AXStaticText").first.invalidate().value.value == "0 items") //// save and exit procedures if ((sf.ui.proTools.mainWindow.invalidate().sessionPath !== null || sf.ui.proTools.getMenuItem('File', 'Close Session').isEnabled) || sf.ui.proTools.confirmationDialog.exists) { sf.ui.proTools.waitForNoModals(); sf.ui.proTools.menuClick({ menuPath: ["File", "Save"] }); sf.ui.proTools.menuClick({ menuPath: ["File", "Close Session"] }); if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Save').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Save').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Cancel').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Cancel').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } sf.ui.proTools.waitForNoModals(); } else { break } sf.wait({ intervalMs: 500 }) } } function waitForOpenSession(m1, m2) { while (!sf.ui.proTools.getMenuItem(m1, m2).isEnabled) { // Reset menu sf.keyboard.press({ keys: "left" }); sf.wait({ intervalMs: 100 }); } } function saveCloseAndOpenAgain(sessionDir) { saveAndClose() sf.ui.proTools.mainWindow.elementWaitFor({ waitType: "Disappear", timeout: -1 }); sf.file.open({ path: sessionDir }); // Wait fo menus to become enabled, thus sessioon is fully opened waitForOpenSession('File', 'Save'); } function main() { saveSessionAsNewVersion(); sf.ui.proTools.appActivateMainWindow(); openFolderTracks(); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show Only", "Inactive Tracks"], }); setGroupActive("<ALL>", true); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Edit Keyboard Focus").first.mouseClickElement({ relativePosition: { "x": -10, "y": 7 }, anchor: "BottomLeft", }); setGroupActive("<ALL>", false); selectInactiveTracks(); sf.ui.proTools.trackDelete(); dismissDialog("Active clips were found", "Delete"); dismissDialog("The track", "Delete"); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show All Tracks"], }); deletePlaylistesMain() sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Select", "\"Select Unused\" Ignores Parent Clip"], targetValue: "Disable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog("REMOVE", "Remove"); /// Need task manager to close sessions if (!sf.ui.proTools.getMenuItem("Window", "Task Manager").isMenuChecked) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Task Manager"], targetValue: "Enable" }); } var sessionDir = sf.ui.proTools.mainWindow.sessionPath saveCloseAndOpenAgain(sessionDir); sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog("REMOVE", "Remove"); sf.keyboard.press({ keys: "left" }); sf.ui.proTools.menuClick({ menuPath: ["Window", "Task Manager"], targetValue: "Disable", }); } main();
Rock on!
- GGabriel Lundh @Gabriel_Lundh
I know I say it alot, but You ROCK @Kitch !
Such an informative and solid rearrangement that will give me a lot of assets to build better scripts in the future. Thanks also for the Dissmiss Dialogue head-up, really helpful reminder of the power of functions!I'll bump you in a few weeks - can't wait to see what you could cook up for the dialogs.
All the best,
GabrielKitch Membery @Kitch2022-01-12 22:07:06.956Z
Back at you mate!!! :-)
- In reply toGabriel_Lundh⬆:
Kitch Membery @Kitch2022-01-12 22:12:46.236Z
One other small note;
In the main function the line of code;
sf.ui.proTools.appActivateMainWindow
should be changed to;
sf.ui.proTools.appActivateMainWindow()
as
appActivateMainWindow()
is a method not a property. Without the()
it will do nothing:-)
- GGabriel Lundh @Gabriel_Lundh
Hahahah, thinking back, I remember that that specific line was the first piece of code I wrote in soundflow from scratch, as an exersice - otherwise I've always copied from a macro to Java or from the forum, hahah! Learning by doing...
Thanks as always, Kitch!!All the best,
GabrielKitch Membery @Kitch2022-01-12 22:20:47.200Z
Keep up the great work @Gabriel_Lundh!
- GGabriel Lundh @Gabriel_Lundh
Hey master @Kitch !
Hope you've gotten some rest after the brilliant work of the new Soundflow release!
I just wanted to bump you here per our conversation above regarding the dialogs.
Do you need me to compile all potential pop-ups or do you have another way around this?Let me know what I can do to assist you!
Thanks as always!
Best,
GabrielKitch Membery @Kitch2022-02-01 21:49:19.790Z
Hi @Gabriel_Lundh,
Thanks for the kind words! Glad you are liking SF5. :-)
Yes if you can compile a list of the popups that would be great. Here is the information I'll need for each dialog.
Dialog screenshot:
Dialog window title:
Dialog description text:
Button you want pressed to dismiss:
Steps to make the Dialog appear:Rock on!
- GGabriel Lundh @Gabriel_Lundh
Hey @Kitch !
Hope you are doing well!
I've been wanting to get to this earlier, but could not until today.
I just tried to recreate all the potential issues of dialogs and after adding one more - the script seems to works quite solid.
I have added a "Set track size" to small as this throws off the delete playlist function - and that solved a lot of problems.So for now I think this is quite solid - I will try it in the field for a few days and see if it works in the real world!
Here is the "final" script as of now:
function saveSessionAsNewVersion() { //First save the current version sf.ui.proTools.getMenuItem('File', 'Save').elementClick({}, function () { }); //can be disabled //Get our filename without extension var fileNameWithoutExtension = sf.ui.proTools.mainWindow.invalidate().sessionPath.split('/').slice(-1)[0].split('.').slice(0, -1).join('.'); //Create a new name (get the name and suffixed number - if your session name was 'My Session 42' then save 'My Session ' in grps[1] and '42' in grps[2]) var grps = fileNameWithoutExtension.match(/^(.+?)(\d+)$/); //Construct a new name based on the prefix and the number (+1), followed by the '.ptx' suffix. If there was no number, just add ' 2' var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? '_2' : ((Number(grps[2]) + 1) + ''); var newName = (grps ? grps[1] : (fileNameWithoutExtension)) + newNumber + '.ptx'; //Invoke File Save As. sf.ui.proTools.getMenuItem('File', 'Save As...').elementClick(); //Wait for the Save dialog to appear var dlg = sf.ui.proTools.windows.invalidate().whoseTitle.is('Save').first.elementWaitFor().element; //Enter the new name dlg.textFields.first.value.value = newName; //Click Save dlg.buttons.whoseTitle.is('Save').first.elementClick(); } function setTrackHeight(size) { // Get name of last track (which has to be the print track) var lastTrackName = sf.ui.proTools.trackNames.slice(-1)[0]; // Scroll the track into view sf.ui.proTools.trackSelectByName({ deselectOthers: true, names: [lastTrackName] }); sf.ui.proTools.selectedTrack.trackScrollToView(); var f = sf.ui.proTools.selectedTrack.frame; var popupMenu = sf.ui.proTools.selectedTrack.popupMenuOpenFromElement({ relativePosition: { x: f.w - 10, y: 5 }, isOption: true, }).popupMenu; popupMenu.menuClickPopupMenu({ menuPath: [size] }); } function selectAllFolderTracks() { var visibleTracks = sf.ui.proTools.trackGetVisibleTracks().names var firstTrackFound; let foundTracks = []; while (true) { sf.ui.proTools.trackDeselectAll(); for (var i = 0; i < visibleTracks.length; i++) { var trackIsFolder = sf.ui.proTools.trackGetByName({ name: visibleTracks[i] }).track.children.whoseRole.is("AXDisclosureTriangle").whoseTitle.is('Open Folder') if (trackIsFolder.exists) if (trackIsFolder) { if (!firstTrackFound) { // Scroll to first track found sf.ui.proTools.trackGetByName({ name: visibleTracks[i], }).track.trackScrollToView(); firstTrackFound = !firstTrackFound; } else { sf.ui.proTools.trackSelectByName({ names: [visibleTracks[i]], deselectOthers: false }); } foundTracks.push(visibleTracks[i]); } } if (foundTracks.length !== 0) { break; } else if (foundTracks.length == 0) { return "exit"; } } } function openFolderTracks() { sf.ui.proTools.appActivateMainWindow(); while (true) { var temp; temp = selectAllFolderTracks(); if (temp == "exit") { return 0; } const selectedTracks = sf.ui.proTools.selectedTrackNames if (selectedTracks.length >= 1) { if (selectedTracks.length == 1) { sf.ui.proTools.selectedTrack.folderTrackSetOpen({ targetValue: 'Enable', onError: 'Continue' }); } if (selectedTracks.length >= 2) { for (var i = 0; i < selectedTracks.length; i++) { sf.ui.proTools.invalidate().trackGetByName({ name: selectedTracks[i] }).track.folderTrackSetOpen({ targetValue: 'Enable', }); } } } } } function setGroupActive(groupName, targetValue) { let grp = sf.ui.proTools.mainWindow.groupListView.childrenByRole('AXRow').map(r => { let btn = r.childrenByRole('AXCell').allItems[1].buttons.first; return { groupName: r.childrenByRole('AXCell').allItems[1].buttons.first.title.value.split(/-\s/)[1], btn, isActive: btn.title.invalidate().value.indexOf('Active ') === 0, row: r, }; }).filter(g => g.groupName === groupName)[0]; if (!grp) throw `Group with name "${groupName}" was not found`; if (grp.isActive !== !!targetValue) grp.btn.elementClick(); } function isTrackActive(trackName) { const track = sf.ui.proTools.trackGetByName({ name: trackName }).track const muteBtn = track.buttons.whoseTitle.is("Mute").first; let isInactive = muteBtn.exists ? track.buttons.whoseTitle.is("Mute").first.value.value.startsWith('inactive') : false; return { trackName: trackName, isInactive: isInactive } } function selectInactiveTracks() { sf.ui.proTools.appActivateMainWindow(); const selectedTrackNames = sf.ui.proTools.selectedTrackNames; const trackActiveStates = selectedTrackNames.map(trackName => isTrackActive(trackName)); const inactiveTrackNames = trackActiveStates.filter(track => track.isInactive).map(track => track.trackName); sf.ui.proTools.trackSelectByName({ names: inactiveTrackNames }); } function dismissDialog(dialogText, buttonName) { const dlg = sf.ui.proTools.confirmationDialog; //Wait 100ms for dialog box to appear dlg.elementWaitFor({ timeout: 100, pollingInterval: 10, onError: 'Continue' }); if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) { dlg.buttons.whoseTitle.is(buttonName).first.elementClick(); dlg.elementWaitFor({ waitType: 'Disappear', timeout: 100, pollingInterval: 10, onError: 'Continue' }); } } function selectAllTracks() { const groupsList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const firstRow = groupsList.childrenByRole("AXRow"); const firstCell = firstRow.first.childrenByRole("AXCell").first.buttons.first; firstCell.elementClick(); } function goToStartOfSession() { sf.ui.proTools.transportEnsureCluster(); const trannsportCluster = sf.ui.proTools.mainWindow.transportViewCluster; const transportButtons = trannsportCluster.groups.whoseTitle.is("Normal Transport Buttons").first; transportButtons.buttons.whoseTitle.is("Return to Zero").first.elementClick(); } function setWindowView() { sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Track List"], targetValue: "Enable" }); sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable" }); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Track List pop-up").first.popupMenuSelect({ menuPath: ["Show All Tracks"], }); function showItems() { function show(item) { sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Show", item], targetValue: "Enable" }); } show("Audio") show("MIDI") show("Video") show("Groups") show("Auto-Created") } showItems() } /** * @param {string} trackType - All track types included on trackTypes array */ function deleteUnusedPlaylists(trackType) { // Check if there are any tracks of trackType const trackTypeExists = sf.ui.proTools.visibleTrackHeaders.some(x => x.title.invalidate().value.includes(trackType)) if (trackTypeExists) { // Get name of first track of trackType const firstTrackByTypeName = sf.ui.proTools.visibleTrackHeaders.filter(x => x.title.invalidate().value.includes(trackType))[0].normalizedTrackName // Find track by name const getFirstTrackByTypeName = sf.ui.proTools.trackGetByName({ name: firstTrackByTypeName }).track // Scrool track if needed getFirstTrackByTypeName.trackScrollToView() // Delete unused menu getFirstTrackByTypeName.popupButtons.whoseTitle.is("Playlist selector").first.popupMenuSelect({ menuPath: ["Delete Unused..."] }); // Delete unused window const deleteUnusedWin = sf.ui.proTools.windows.whoseTitle.is("Delete Unused Playlists").first if (deleteUnusedWin.exists) { const deleteUnusedWinTable = deleteUnusedWin.tables.first.children.whoseRole.is("AXRow") // Select all unused playlists deleteUnusedWinTable.map(x => x.children.first.children.whoseRole.is("AXStaticText").first.elementClick()) // Click delete Btn deleteUnusedWin.buttons.whoseTitle.is("Delete").first.elementClick(); }; }; }; function deletePlaylistesMain() { sf.ui.proTools.appActivateMainWindow(); // show Track list, Clip list and All tracks setWindowView() // Clear all items from clip list // delete unused playlists, needs to be done on all track types const trackTypes = [ "Video Track", "MIDI Track", "Inst Track", "Audio Track" ] trackTypes.forEach(deleteUnusedPlaylists) } function saveAndClose() { while (true) { //// wait for complete tasks if (sf.ui.proTools.windows.whoseTitle.is('Task Manager').first.children.whoseRole.is("AXStaticText").first.invalidate().value.value == "0 items") //// save and exit procedures if ((sf.ui.proTools.mainWindow.invalidate().sessionPath !== null || sf.ui.proTools.getMenuItem('File', 'Close Session').isEnabled) || sf.ui.proTools.confirmationDialog.exists) { sf.ui.proTools.waitForNoModals(); sf.ui.proTools.menuClick({ menuPath: ["File", "Save"] }); sf.ui.proTools.menuClick({ menuPath: ["File", "Close Session"] }); if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Save').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Save').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } if (sf.ui.proTools.confirmationDialog.invalidate().buttons.whoseTitle.is('Cancel').first.exists) { sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('Cancel').first.elementClick(); sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: "Disappear", onError: "Continue" }); } sf.ui.proTools.waitForNoModals(); } else { break } sf.wait({ intervalMs: 500 }) } } function waitForOpenSession(m1, m2) { while (!sf.ui.proTools.getMenuItem(m1, m2).isEnabled) { // Reset menu sf.keyboard.press({ keys: "left" }); sf.wait({ intervalMs: 100 }); } } function saveCloseAndOpenAgain(sessionDir) { saveAndClose() sf.ui.proTools.mainWindow.elementWaitFor({ waitType: "Disappear", timeout: -1 }); sf.file.open({ path: sessionDir }); // Wait fo menus to become enabled, thus sessioon is fully opened waitForOpenSession('File', 'Save'); } function main() { saveSessionAsNewVersion(); sf.ui.proTools.appActivateMainWindow(); setTrackHeight("small") openFolderTracks(); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show Only", "Inactive Tracks"], }); setTrackHeight("small") setGroupActive("<ALL>", true); sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Edit Keyboard Focus").first.mouseClickElement({ relativePosition: { "x": -10, "y": 7 }, anchor: "BottomLeft", }); setGroupActive("<ALL>", false); selectInactiveTracks(); sf.ui.proTools.trackDelete(); dismissDialog("A folder and all", "Delete"); dismissDialog("Active clips were found", "Delete"); dismissDialog("The track", "Delete"); var popupMenu = sf.ui.proTools.trackOpenListPopupMenu().popupMenu; popupMenu.menuClickPopupMenu({ menuPath: ["Show All Tracks"], }); deletePlaylistesMain() sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Select", "\"Select Unused\" Ignores Parent Clip"], targetValue: "Disable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog("REMOVE", "Remove"); /// Need task manager to close sessions if (!sf.ui.proTools.getMenuItem("Window", "Task Manager").isMenuChecked) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Task Manager"], targetValue: "Enable" }); } var sessionDir = sf.ui.proTools.mainWindow.sessionPath saveCloseAndOpenAgain(sessionDir); sf.ui.proTools.menuClick({ menuPath: ["View", "Other Displays", "Clip List"], targetValue: "Enable", }); sf.keyboard.press({ keys: "cmd+shift+u", }); sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is('Clip List').first.popupMenuSelect({ menuPath: ["Clear..."], }); dismissDialog("REMOVE", "Remove"); sf.keyboard.press({ keys: "left" }); sf.ui.proTools.menuClick({ menuPath: ["Window", "Task Manager"], targetValue: "Disable", }); sf.keyboard.press({ keys: "enter", }); } main();
Thanks as always for your help!
All the best,
GabrielBrett Ryan Stewart @Brett_Ryan_Stewart
@Gabriel_Lundh Just wanted to report back that this new script seems to work pretty seamless so far. Thank you!!
- GGabriel Lundh @Gabriel_Lundh
@Brett_Ryan_Stewart Hi man! Thank you for reporting back! I've found some additional bugs that I will look into - for example if the first inactive track is an Instrument track, the script fails to remove the inactive tracks... I'll let you know once it's updated!
Have a great weekend!
- In reply toGabriel_Lundh⬆:PPhilip weinrobe @Philip_weinrobe
Hi!
I've been trying to implement this script into a larger "session flattening" process I am working on.
I am kinda clumsy with the code though!Can you (or someone!) help me figure out a way to remove the following parts of this script:
- The beginning save-as function. (I have my own save-as function I am going to use instead)
- The ending "close session" process. (I have additional steps I want to take!)
Thank you!
Philip- SSreejesh Nair @Sreejesh_Nair
Maybe you could try commenting out line 310 and 378 with // to check?
- SIn reply toGabriel_Lundh⬆:Sean McDonald @Sean_McDonald5
Hi guys!
Did a search for the script, and luckily you talented people are working on it.But after installing the latest version, I am getting this error.
Perhaps it’s something I screwed up when copying and pasting?
I tried twice, and I’m getting the same results each time. - SIn reply toGabriel_Lundh⬆:Sean McDonald @Sean_McDonald5
Hello All,
Just bumping my experiences with this script, any idea what I might be doing wrong?
thanks in advance!- GGabriel Lundh @Gabriel_Lundh
Hi @Sean_McDonald5 !
Sorry for not getting back to you here!
The first thing that throws the script now is the new "Save Session As" instead of "Save As" in the new Pro Tools, - so you can change that on Line 17.
Then I seem to get additional errors after this in the new Pro Tools which were not quick to fix...
Unfortunately, I'm not using this script any more as I more often Select the tracks I want to keep, then I do a Save Copy in with "Selected Tracks Only"...But I might have time to look this over later this week, I'll keep you updated in that case!
Thanks and have a great day!
- SSean McDonald @Sean_McDonald5
Thanks!👍👍🙏🙏