Delete between markers
I posted this as a follow up to someone else's question that was pretty similar but I think it got buried. If there is a post out there with this already I can certainly work off that. Thanks
"Anyone know how to apply this to deleting selections and then moving on to the next selection? For example, in most of my shows I want to make sure there is no audio within the blacks between the act breaks. I usually have "break 1", "ACT 2", "break 2", "ACT 3" markers etc. Ideally I'd like to make a selection of tracks and have the selection go to "break 1" then select until "ACT 2" then delete, then move on to "break 2" and make the selection from marker "break2" to marker "ACT3" and so on....
- Ddanielkassulke @danielkassulke
Hi @Chris_Testa , try this one, courtesy of @Kitch, tweaking by me :
const startMemoryLocationKeyword = "ACT"; const endMemoryLocationKeyword = "BREAK"; sf.ui.proTools.memoryLocationsWindow.invalidate(); function isAudioTrack(track) { return track.title.value.indexOf('Audio Track') >= 0; } function selectAllAudioTracks() { var tracksToSelect = sf.ui.proTools.visibleTrackHeaders.filter(isAudioTrack); sf.ui.proTools.trackSelectByName({ names: tracksToSelect.map(t => t.normalizedTrackName), deselectOthers: true, }); } function executeSelectAudioTracks() { sf.ui.proTools.appActivateMainWindow(); selectAllAudioTracks(); } function validateMemoryLocationArray(locations, startMemoryLocationKeyword, endMemoryLocationKeyword) { const locationNames = locations.map(m => m.name); if (locationNames.length < 2) { throw `There must be at least one pair of "${startMemoryLocationKeyword}" and "${endMemoryLocationKeyword}" markers.`; } if (!locationNames[0].includes(startMemoryLocationKeyword)) { throw `The first location name must contain "${startMemoryLocationKeyword}".`; } for (let i = 1; i < locationNames.length; i++) { const expectedKeyword = i % 2 === 1 ? endMemoryLocationKeyword : startMemoryLocationKeyword; if (!locationNames[i].includes(expectedKeyword)) { throw `Location at index ${i} must contain '${expectedKeyword}'.`; } } return true; } function selectAndProcessSection({ startMemoryLocation, endMemoryLocation, isDeadSpace }) { let errorMessage = ""; if (!startMemoryLocation) { errorMessage += `Start memory location containing "${startMemoryLocationKeyword}" not found. `; } if (!endMemoryLocation) { errorMessage += `End memory location containing "${endMemoryLocationKeyword}" not found.`; } if (errorMessage) { throw new Error(errorMessage.trim()); } executeSelectAudioTracks(); const inTime = startMemoryLocation.startTime; const outTime = endMemoryLocation.startTime; sf.app.proTools.invalidate(); sf.app.proTools.setTimelineSelection({ inTime, outTime }); // clear timeline selection if between "BREAK" and "ACT" if (isDeadSpace) { sf.ui.proTools.menuClick({ menuPath: ["Edit", "Clear"], }); } } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); sf.app.proTools.invalidate(); const memoryLocations = sf.app.proTools.memoryLocations.allItems; const filteredMemoryLocations = memoryLocations.filter(ml => { const name = ml.name; return name.includes(startMemoryLocationKeyword) || name.includes(endMemoryLocationKeyword); }); validateMemoryLocationArray(filteredMemoryLocations, startMemoryLocationKeyword, endMemoryLocationKeyword); for (let i = 0; i < filteredMemoryLocations.length - 1; i++) { let startMemoryLocation = filteredMemoryLocations[i]; let endMemoryLocation = filteredMemoryLocations[i + 1]; const isDeadSpace = startMemoryLocation.name.includes(endMemoryLocationKeyword) && endMemoryLocation.name.includes(startMemoryLocationKeyword); selectAndProcessSection({ startMemoryLocation, endMemoryLocation, isDeadSpace, }); } } main();
Script will filter out non-alphabetical characters from the memory locations, if your labelling conventions look like mine, i.e. "1. ACT 1", "2. BREAK 1" etc. The script will also ignore any markers between BREAK and ACT that don't have those words in them. Note that it's currently operating on all audio tracks - you might want to tweak this, but i figured that dead air is dead air. Tested on 2024.10, SF V5.9.0
- CChris Testa @Chris_Testa
Amazing. Thank you. Trying out now. I'll let you know. thanks.
- In reply todanielkassulke⬆:CChris Testa @Chris_Testa
So I got an error. I switched the "BREAK" to "break" and still the same error. I've included the error and what my Markers list looks like. This is baked into my template so it won't really change.
- Ddanielkassulke @danielkassulke
OK, slight rethink:
const startMemoryLocationKeyword = "ACT"; const endMemoryLocationKeyword = "BREAK"; sf.ui.proTools.memoryLocationsWindow.invalidate(); function isAudioTrack(track) { return track.title.value.indexOf('Audio Track') >= 0; } function selectAllAudioTracks() { var tracksToSelect = sf.ui.proTools.visibleTrackHeaders.filter(isAudioTrack); sf.ui.proTools.trackSelectByName({ names: tracksToSelect.map(t => t.normalizedTrackName), deselectOthers: true, }); } function executeSelectAudioTracks() { sf.ui.proTools.appActivateMainWindow(); selectAllAudioTracks(); } function selectAndProcessSection({ startMemoryLocation, endMemoryLocation }) { executeSelectAudioTracks(); const inTime = startMemoryLocation.MainCounterValue; const outTime = endMemoryLocation.MainCounterValue; sf.app.proTools.invalidate(); sf.app.proTools.setTimelineSelection({ inTime, outTime }); sf.ui.proTools.menuClick({ menuPath: ["Edit", "Clear"], }); } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); sf.app.proTools.invalidate(); // Fetch and retrieve all memory locations const memoryLocationsData = sf.proTools.memoryLocationsFetch().collection['List']; const actRegex = /\bact\b/i; const breakRegex = /\bbreak\b/i; const filteredMemoryLocations = memoryLocationsData .filter(ml => actRegex.test(ml.Name) || breakRegex.test(ml.Name)) .sort((a, b) => parseInt(a.MainCounterValue) - parseInt(b.MainCounterValue)); let processing = false; for (let i = 0; i < filteredMemoryLocations.length - 1; i++) { let breakMarker = filteredMemoryLocations[i]; if (!processing && actRegex.test(breakMarker.Name)) { processing = true; } if (processing && breakRegex.test(breakMarker.Name)) { for (let j = i + 1; j < filteredMemoryLocations.length; j++) { let actMarker = filteredMemoryLocations[j]; if (actRegex.test(actMarker.Name)) { selectAndProcessSection({ startMemoryLocation: breakMarker, endMemoryLocation: actMarker, }); i = j; break; } } } } } main();
- CChris Testa @Chris_Testa
ok now nothing happens. No trigger, no error. Humm. not sure. Do I need to modify something on my end?
- Ddanielkassulke @danielkassulke
edited above - suspect it might have been case-sensitivity that was throwing it off
- CChris Testa @Chris_Testa
No. Still nothing. It's weird, no error. It's like it doesn't fire. I tried running directly from SF Editor and then added to my deck and still nothing. Not sure.
- Ddanielkassulke @danielkassulke
What version of PT / SF are you running?
- CChris Testa @Chris_Testa
PT 2024.3 and SF 5.9
- Ddanielkassulke @danielkassulke
Ahhhh ok. Gotcha. 2024.6 Changed a fair bit of stuff in the memory location window, so all of my memory location scripts are optimised to handle 2024.6+. I won't be able to test backwards compatibility, but give this script a test drive when you have updated PT. Not to tell you how to cook your own BBQ, but you could download PT 2024.6 and rename the app to "Pro Tools 2024.6" and test it alongside 2024.3 if you were interested.
- CChris Testa @Chris_Testa
ok. Yeah I was having some HDX issues with 2024.6 so I moved back. Thinking of going straight to 2024.10. Thank you again and I will.
- In reply todanielkassulke⬆:CChris Testa @Chris_Testa
Ok so I updated this morning to 2024.10 and it works perfectly. Thank you again. chris
- Ddanielkassulke @danielkassulke
No probs! I'll share one with you that automatically creates those act/break markers too - almost finished it, but have a couple of problems to solve first.
- In reply toChris_Testa⬆:Ddanielkassulke @danielkassulke
const pT = sf.ui.proTools; pT.appActivateMainWindow(); pT.mainWindow.invalidate(); function isAudioTrack(track) { return track.title.value.indexOf('Audio Track') >= 0; } function selectAllAudioTracks() { const tracksToSelect = sf.ui.proTools.visibleTrackHeaders.filter(isAudioTrack); sf.ui.proTools.trackSelectByName({ names: tracksToSelect.map(t => t.normalizedTrackName), deselectOthers: true, }); } function getMemoryLocations() { sf.app.proTools.memoryLocations.invalidate(); return sf.app.proTools.memoryLocations.allItems.map(memLoc => ({ number: memLoc.number, name: memLoc.name })); } function newMemoryLocation(name, startTime, markerNumber) { const locationMarkerName = `${markerNumber}. ${name}`; sf.app.proTools.createMemoryLocation({ name: locationMarkerName, startTime: startTime.toString(), colorIndex: 16 }); } function newSelectionMarker(name, inTime, outTime) { selectAllAudioTracks(); sf.app.proTools.setTimelineSelection({ inTime: inTime.toString(), outTime: outTime.toString() }); sf.keyboard.press({ keys: "numpad enter" }); const memLocWin = sf.ui.proTools.newMemoryLocationDialog.elementWaitFor().element; memLocWin.textFields.first.elementSetTextFieldWithAreaValue({ value: name }); memLocWin.radioButtons.whoseTitle.is("Selection").first.checkboxSet({ targetValue: "Enable" }); memLocWin.buttons.whoseTitle.is("OK").first.elementClick(); memLocWin.elementWaitFor({ waitType: "Disappear" }); } function getOverallInOutTimes() { selectAllAudioTracks(); sf.keyboard.press({ keys: "left" }); pT.menuClick({ menuPath: ["Edit", "Select All"] }); const timelineSelection = sf.app.proTools.getTimelineSelection(); return { inTime: parseFloat(timelineSelection.inTime), outTime: parseFloat(timelineSelection.outTime) }; } function getSegmentBoundaries() { const { inTime: overallInTime, outTime: overallOutTime } = getOverallInOutTimes(); const selectedClips = sf.app.proTools.getSelectedClipInfo().Clips.map(clip => ({ startTime: parseFloat(clip.startTime), endTime: parseFloat(clip.endTime) })).sort((a, b) => a.startTime - b.startTime); const currentSampleRate = sf.app.proTools.getSessionSampleRate().sampleRate; const sampleRateNumber = Number(currentSampleRate.match(/\d+/)[0]); const gapThresholdSamples = 30 * sampleRateNumber; const segments = []; let segmentStartTime = selectedClips[0].startTime; let segmentEndTime = selectedClips[0].endTime; for (let i = 1; i < selectedClips.length; i++) { const currentClip = selectedClips[i]; const gap = currentClip.startTime - segmentEndTime; if (gap > gapThresholdSamples) { segments.push({ startTime: segmentStartTime, endTime: segmentEndTime }); segmentStartTime = currentClip.startTime; } segmentEndTime = Math.max(segmentEndTime, currentClip.endTime); } segments.push({ startTime: segmentStartTime, endTime: segmentEndTime }); segments[0].startTime = Math.min(segments[0].startTime, overallInTime); segments[segments.length - 1].endTime = Math.max(segments[segments.length - 1].endTime, overallOutTime); return segments; } function createMarkers() { const segments = getSegmentBoundaries(); const startMarkerNumber = getMemoryLocations().reduce((max, mem) => Math.max(max, mem.number), 0) + 1; let markerNumber = startMarkerNumber; segments.forEach((segment, index) => { newMemoryLocation(`ACT ${index + 1}`, segment.startTime, markerNumber); markerNumber += 1; if (index < segments.length - 1) { newMemoryLocation(`BREAK ${index + 1}`, segment.endTime, markerNumber); markerNumber += 1; } }); segments.forEach((segment, index) => { newSelectionMarker(`ACT ${index + 1}`, segment.startTime, segment.endTime); }); } createMarkers();
Hey @Chris_Testa Give this a spin next time you want to create those markers automatically. This script will:
- Create ACT location markers
- Create BREAK location markers
- Create ACT selection markers
Any part of your timeline containing no clips for more than 30 seconds will be considered a break. You can adjust this on
line 71 const gapThresholdSamples
.A couple of things to be mindful of - clips on inactive audio tracks will be considered part of the timeline, so might throw the timeline selection off. Also, if your audio tracks are in closed folders this script will fail.
- CChris Testa @Chris_Testa
That's really interesting. I might be able to use that on a REF mix to lay in markers then bring everything else in. Are you mixing TV?
- Ddanielkassulke @danielkassulke
Not full time, though will do a few TV gigs per year. Enough that I became acutely aware of how much time I was wasting without scaffolding my workflow with soundflow
- CChris Testa @Chris_Testa
Right. SF is essential for post now. So many repetitive tasks. Every day I'm adding something else. Thank you again for your help with this one.