No internet connection
  1. Home
  2. How to

Separate clips by cut track - separation not happening

By Martin Wrang @Martin_Wrang
    2024-12-08 13:58:17.699Z

    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
        });
    }
    
    • 10 replies

    There are 10 replies. Estimated reading time: 23 minutes

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

      1. MMartin Wrang @Martin_Wrang
          2024-12-09 21:38:40.911Z

          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.

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

            1. Kitch Membery @Kitch2024-12-10 19:28:06.118Z

              Ohhh thanks man!!

              1. MMartin Wrang @Martin_Wrang
                  2024-12-10 20:15:53.117Z

                  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.

                  1. 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
                    2024-12-10 20:09:36.029Z

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

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

                      1. MMartin Wrang @Martin_Wrang
                          2025-01-20 18:35:50.215Z

                          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.