Separate clips by cut track - separation not happening
I modified the 'Split by markers' script to split by clip groups on a cut-track instead. The script runs successfully, but the separation is not actually happening. I also tried using a 'cmd + e' keyboard input instead of clicking the menu item as well as setting wait periods before and after the command.
Can anyone help me find the issue here?
sf.ui.proTools.appActivateMainWindow();
sf.ui.proTools.mainWindow.invalidate();
// Sets original track selection for recall
const originalTrack = sf.ui.proTools.trackGetSelectedTracks();
//Store selection
let originalSelection = sf.ui.proTools.selectionGet();
let originalSelectionSamples = sf.ui.proTools.selectionGetInSamples();
//Ensure the user has actually made a selection within which we should operate
if (originalSelectionSamples.selectionLength === 0)
throw `Please make a selection before running this script`;
try {
//Collapse selection to the left hand side
sf.ui.proTools.selectionSet({
selectionStart: originalSelection.selectionStart,
selectionEnd: originalSelection.selectionStart,
});
//Perform an endless loop
while (true) {
// Go to CUT track
sf.ui.proTools.trackSelectByName({
names: ["CUT"],
deselectOthers: true,
});
//Go to the clip
let goRes = sf.keyboard.press({
keys: 'ctrl + tab'
})
//If we didn't find a next memory location, just abort the loop
if (!goRes.success)
break;
//Retrieve the selection at this point
let newSelection = sf.ui.proTools.selectionGet();
//If we're now further to the right than the original selection,
//break the loop
if (newSelection.selectionEnd > originalSelection.selectionEnd)
break;
//Otherwise, click Separate Clip At Selection, and continue the loop
//also - if for some reason we're in between clips we're allowing the menu click to fail silently
// Go to original tracks
sf.ui.proTools.trackSelectByName({
names: originalTrack.names.slice(0),
deselectOthers: true
});
// sf.wait({ intervalMs: 300, });
sf.ui.proTools.menuClick({
menuPath: ["Edit", "Separate Clip", "At Selection"],
onError: 'Continue',
});
// sf.wait({ intervalMs: 300, });
//Continue the loop
}
} finally {
//Finally, regardless of any error occurring in the previous code,
//restore the original selection
sf.ui.proTools.selectionSetInSamples({
selectionStart: originalSelectionSamples.selectionStart,
selectionEnd: originalSelectionSamples.selectionEnd,
});
// Go to original tracks
sf.ui.proTools.trackSelectByName({
names: originalTrack.names.slice(0),
deselectOthers: true
});
}
- Kitch Membery @Kitch2024-12-09 20:49:38.072Z
Hi @Martin_Wrang,
At first glance, I can see a couple of potential issues with the script.
First, It looks like your try/finally block has no catch block.
"try/catch/finally" needs to have a catch block. Like this....
try { } catch (err){ } finally{ }
And there is also a chance that the Pro Tools menus are not updating after the selections are being made. I'll take a look at the script shortly to see if I can work out what's going on. :-)
- MMartin Wrang @Martin_Wrang
Hi Kitch
I stumbled upon a temporary solution to the menus not updating in another script. It's in line 69 below. Don't know if there's a better solution now.
I don't understand the try/catch/finally logic at all, so any help there would be much appreciated!const pt = sf.ui.proTools pt.appActivateMainWindow(); pt.mainWindow.invalidate(); // Sets original track selection for recall const originalTrack = pt.trackGetSelectedTracks(); // Select clip on SCENE track var refTrack = 'SCENE' const newTrack = pt.trackSelectByName({ names: [refTrack], deselectOthers: true, }) sf.keyboard.press({ keys: 'ctrl + alt + tab' }); //Store SCENE selection let originalSelection = pt.selectionGet(); let originalSelectionSamples = pt.selectionGetInSamples(); //Ensure the user has actually made a selection within which we should operate if (originalSelectionSamples.selectionLength === 0) throw `Please make a selection before running this script`; try { //Collapse selection to the left hand side pt.selectionSet({ selectionStart: originalSelection.selectionStart, selectionEnd: originalSelection.selectionStart, }); //Perform an endless loop while (true) { // Go to CUT track pt.trackSelectByName({ names: ["CUT"], deselectOthers: true, }); //Go to next clip let goRes = sf.keyboard.press({ keys: 'quote' }) //If we didn't find a next memory location, just abort the loop if (!goRes.success) break; //Retrieve the selection at this point let newSelection = pt.selectionGet(); //If we're now further to the right than the original selection, //break the loop if (newSelection.selectionEnd >= originalSelection.selectionEnd) break; //Otherwise, click Separate Clip At Selection, and continue the loop //also - if for some reason we're in between clips we're allowing the menu click to fail silently // Go to original tracks pt.trackSelectByName({ names: originalTrack.names.slice(0), deselectOthers: true }); ///Re-evaluate here... HACK TO REFRESH MENUS sf.keyboard.press({ keys: 'n', fast: true, repetitions: 2 }); // sf.wait({ intervalMs: 300, }); pt.menuClick({ menuPath: ["Edit", "Separate Clip", "At Selection"], onError: 'Continue', }); // sf.wait({ intervalMs: 300, }); //Continue the loop } } finally { //Finally, regardless of any error occurring in the previous code, //restore the original selection pt.selectionSetInSamples({ selectionStart: originalSelectionSamples.selectionStart, selectionEnd: originalSelectionSamples.selectionEnd, }); // Go to original tracks pt.trackSelectByName({ names: originalTrack.names.slice(0), deselectOthers: true }); sf.keyboard.press({ keys: 'f' }) }
This script is part of a larger change I'd like to make to my workflow that makes it easier to navigate and make selections based on cuts & scenes - and to perform actions such as spotting from Soundly, separating clips etc.
If possible I think using markers, instead of clip groups on dedicated cut/scene-tracks, would be a more elegant solution. I searched around for scripts for navigating markers with specific text or on a specific marker lane. I don't really understand what goes into that with arrays and filtering, but so far using clip groups have been faster and more reliable for me.
I also replied to a script you posted in the thread linked below with these workflows in mind.
Searching/Navigating Between Marker Text in Soundflow?
- In reply toMartin_Wrang⬆:Kitch Membery @Kitch2024-12-09 21:49:07.799Z
Hi @Martin_Wrang,
I put together a script that should do what you're after. It uses the Pro Tools API, and should work much faster.
const cutTrackName = "CUT" // Activate Pro Tools' main window sf.ui.proTools.appActivateMainWindow(); // Invalidate Pro Tools main window sf.ui.proTools.mainWindow.invalidate(); // Invalidate the Pro Tools API sf.app.proTools.invalidate(); // Sets original track selection for recall const originalSelectedTrackNames = sf.app.proTools.tracks.allItems.filter(track => track.isSelected).map(track => track.name); //Store selection let timelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); // Destructure the inTime and outTime from the timeline selection const { inTime, outTime } = timelineSelection; // Calculate the selection length const selectionLength = Number(outTime) - Number(inTime); //Ensure the user has a timeline selection if (selectionLength <= 0) throw "Please make a selection before running this script"; // Select the cut track sf.app.proTools.selectTracksByName({ trackNames: [cutTrackName] }); // Get the selected clips in the cut track const clipsInfo = sf.app.proTools.getSelectedClipInfo().clips; // Initialize an array for the cut/separation points const cutPoints = []; // For each clip, add start and end times as cutpoints if they fall within the original selection clipsInfo.forEach(clip => { if (clip.startTime >= inTime && clip.endTime <= outTime) { // Append clip’s start time to cut points if (!cutPoints.includes(clip.startTime)) cutPoints.push(clip.startTime) // Append clip’s end time to cut points. if (!cutPoints.includes(clip.endTime)) cutPoints.push(clip.endTime) } }); // Reselect original tracks sf.app.proTools.selectTracksByName({ trackNames: originalSelectedTrackNames }); // Make cuts/separations cutPoints.forEach(cutPoint => { sf.app.proTools.setTimelineSelection({ inTime: String(cutPoint), outTime: String(cutPoint), }) // Separate at selection sf.ui.proTools.menuClick({ menuPath: ["Edit", "Separate Clip", "At Selection"], onError: 'Continue', }); // Wait for the separation to be made by waiting for the the ["Edit", "Separate Clip", "At Selection"] menu item to be disabled. sf.waitFor({ callback: () => !sf.ui.proTools.getMenuItem("Edit", "Separate Clip", "At Selection").isEnabled, timeout: 1000, }); }); log("Done");
Let me know if that works for you. :-)
I just saw your last reply to this thread (after finishing this script). It looks like you're possibly wanting this to work with markers instead of clips. Let me know if that is the case.
- OOwen Granich-Young @Owen_Granich_Young
@KITCH, I haven't tried it yet, but this script is inspirational. I now have to go completely re-code my =CUTS= package so it stores the cuts track in a .json with the session name once and then recalls from that instead of navigating to the cuts track and keyboard simulating. Will be SO MUCH SLEEKER.
Hats off man.
Kitch Membery @Kitch2024-12-10 19:28:06.118Z
Ohhh thanks man!!
- In reply toOwen_Granich_Young⬆:MMartin Wrang @Martin_Wrang
I got the
///Re-evaluate here... HACK TO REFRESH MENUS sf.keyboard.press({ keys: 'n', fast: true, repetitions: 2 }); sf.ui.proTools.menuClick({ menuPath: ["Edit", "Separate Clip", "At Selection"], });
from your package Owen. Thanks for that!
I'd be interested to see if you can come up with a faster solution for the perspective cuts script. Currently it's too slow to really be useful for me. For now I'll use Kitch's script, followed by a batch fade and then manually ctrl+dragging every other clip to the next track.
- OOwen Granich-Young @Owen_Granich_Young
Yeah there is a faster SDK perspective cut -- I haven't sat down to build it out -- TBH this script from @Kitch is half way there, just need to track select via SDK every other cut and paste instead of separate. When I get around to updated the =CUTS= package based on this script I'll see if I can't also do a level'd up perspective cutter.
Put it on the - Pile of scripts to write when I have time :P
Bests,
Owen
- In reply toKitch⬆:MMartin Wrang @Martin_Wrang
Wow this really performs on a whole other level I must say. Thank you Kitch! I'll have to go back and redo a bunch of other scripts that involve making selections now.
I made this little addition to first select the entire scene, and then cut according to the cut track except for the cuts at the first/last frame of the scene:
const cutTrackName = "CUT" const sceneTrackName = "SCENE" // Activate Pro Tools' main window sf.ui.proTools.appActivateMainWindow(); // Invalidate Pro Tools main window sf.ui.proTools.mainWindow.invalidate(); // Invalidate the Pro Tools API sf.app.proTools.invalidate(); // Sets original track selection for recall const originalSelectedTrackNames = sf.app.proTools.tracks.allItems.filter(track => track.isSelected).map(track => track.name); // Select the Scene track sf.app.proTools.selectTracksByName({ trackNames: [sceneTrackName] }); // Get the selected clips in scene track let scene = sf.app.proTools.getSelectedClipInfo().clips let inTime, outTime if (scene[1] !== undefined) { inTime = String(scene[1].startTime); outTime = String(scene[1].endTime); } else if (scene[0] !== undefined) { inTime = String(scene[0].startTime); outTime = String(scene[0].endTime); } else { throw 'No scene selected' } // Sets timeline selection to current scene sf.app.proTools.setTimelineSelection({ inTime: inTime, outTime: outTime, }) /* //Store selection let timelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); // Destructure the inTime and outTime from the timeline selection const { inTime, outTime } = timelineSelection; // Calculate the selection length const selectionLength = Number(outTime) - Number(inTime); //Ensure the user has a timeline selection if (selectionLength <= 0) throw "Please make a selection before running this script"; */ // Select the cut track sf.app.proTools.selectTracksByName({ trackNames: [cutTrackName] }); // Get the selected clips in the cut track const clipsInfo = sf.app.proTools.getSelectedClipInfo().clips; // Initialize an array for the cut/separation points const cutPoints = []; // For each clip, add start and end times as cutpoints if they fall within the original selection clipsInfo.forEach(clip => { if (clip.startTime > inTime && clip.endTime < outTime) { // Append clip’s start time to cut points if (!cutPoints.includes(clip.startTime)) cutPoints.push(clip.startTime) // Append clip’s end time to cut points. if (!cutPoints.includes(clip.endTime)) cutPoints.push(clip.endTime) } }); // Reselect original tracks sf.app.proTools.selectTracksByName({ trackNames: originalSelectedTrackNames }); // Make cuts/separations cutPoints.forEach(cutPoint => { sf.app.proTools.setTimelineSelection({ inTime: String(cutPoint), outTime: String(cutPoint), }) // Separate at selection sf.ui.proTools.menuClick({ menuPath: ["Edit", "Separate Clip", "At Selection"], onError: 'Continue', }); // Wait for the separation to be made by waiting for the the ["Edit", "Separate Clip", "At Selection"] menu item to be disabled. sf.waitFor({ callback: () => !sf.ui.proTools.getMenuItem("Edit", "Separate Clip", "At Selection").isEnabled, timeout: 1000, }); }); sf.app.proTools.setTimelineSelection({ inTime: inTime, outTime: outTime, }) log("Done");
Changes made to line 66 and added 14-36.
If you think markers can perform the tasks in a similar amount of time I think I'd prefer it. If it's significantly slower, I'd be perfectly happy with using clip groups though. But I would like to be able move and extend my timeline selection between markers containing certain text for other things. e.g. inputting "EXT_Park" to a prompt and then being able to move between only markers with matching text. Like the script below, but hopefully performing as well as this one?
Identical Marker Navigation #post-19Kitch Membery @Kitch2024-12-10 21:17:39.521Z
Hi @Martin_Wrang,
I'm glad it works for you... I'll have to think about a solution involving markers... I won't have time to look at it this week, unfortunately, but please bump this thread next week and I'll see what I can do. :-)
- MMartin Wrang @Martin_Wrang
Hi Kitch
I've taken some code from Owen's SDK marker package to build a version that works with markers:
let markerName = "CUT" let caseSensitive = false sf.ui.proTools.appActivateMainWindow(); //sf.ui.proTools.mainWindow.invalidate(); //sf.app.proTools.invalidate(); //Store selection let timelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" }); // Destructure the inTime and outTime from the timeline selection const { inTime, outTime } = timelineSelection; // Calculate the selection length const selectionLength = Number(outTime) - Number(inTime); //Ensure the user has a timeline selection if (selectionLength <= 0) throw "Please make a selection before running this script"; function getMemoryLocations() { return sf.app.proTools.memoryLocations.invalidate().allItems.map(m => { let mappedProps = {}; for (let prop in m) { const itemsToSkip = ["Parent", "FriendlyNodeName", "SupportsAutoUpdate"]; if (!itemsToSkip.includes(prop)) { const lowerCasePropName = prop.slice(0, 1).toLowerCase() + prop.slice(1); mappedProps[lowerCasePropName] = m[prop]; } } return mappedProps; }); } // Process the memory locations data. mAIN let processedMemoryLocations = getMemoryLocations(); // If Case Sensitive is 'No', convert marker names to lower case if (caseSensitive == false) { markerName = markerName.toLowerCase(); processedMemoryLocations.forEach(item => { item.name = item.name.toLowerCase() }); } //Filter memory locations to only those including markerName in the name let selectedName = processedMemoryLocations .filter(item => item.name.includes(markerName)); // Initialize an array for the cut/separation points const cutPoints = []; // For each clip, add start and end times as cutpoints if they fall within the original selection selectedName.forEach(marker => { if (marker.startTimeInSamples >= inTime && marker.startTimeInSamples <= outTime) { // Append clip’s start time to cut points if (!cutPoints.includes(marker.startTimeInSamples)) cutPoints.push(marker.startTimeInSamples) // Append clip’s end time to cut points. if (!cutPoints.includes(marker.endTimeI)) cutPoints.push(marker.endTime) } }); // Make cuts/separations cutPoints.forEach(cutPoint => { sf.app.proTools.setTimelineSelection({ inTime: String(cutPoint), outTime: String(cutPoint), }) // Separate at selection sf.ui.proTools.menuClick({ menuPath: ["Edit", "Separate Clip", "At Selection"], onError: 'Continue', }); // Wait for the separation to be made by waiting for the the ["Edit", "Separate Clip", "At Selection"] menu item to be disabled. sf.waitFor({ callback: () => !sf.ui.proTools.getMenuItem("Edit", "Separate Clip", "At Selection").isEnabled, timeout: 1000, }); }); sf.app.proTools.setTimelineSelection({ inTime: inTime, outTime: outTime, }) log("Done");
Would it improve performance if I stripped the marker array of any keys except for "name" and "start time"?
I'm also confused as to whether the invalidations in the start of this script, and the one you wrote, is neccesary or how much it impacts the performance of the script. I really don't understand that part of Soundflow/JavaScript at all yet.