Creating an "Import Mix Template" macro.
Title
Creating an "Import Mix Template" macro.
What do you expect to happen when you run the script/macro?
Once the macro has opened the window to import my session data from a mix template, how do I tell the macro to select every track in the mix template and then click the "okay" button?
Are you seeing an error?
What happens when you run this script?
The basic idea is to import all session data from my mix template.
How were you running this script?
I used a keyboard shortcut within the target app
How important is this issue to you?
5
Details
{ "inputExpected": "Once the macro has opened the window to import my session data from a mix template, how do I tell the macro to select every track in the mix template and then click the \"okay\" button?", "inputIsError": false, "inputWhatHappens": "The basic idea is to import all session data from my mix template.", "inputHowRun": { "key": "-Mpfwh4RkPLb2LPwjePT", "title": "I used a keyboard shortcut within the target app" }, "inputImportance": 5, "inputTitle": "Creating an \"Import Mix Template\" macro." }
Source
//Macro converted to script
//Calling command "Import - Session Data..." from package "undefined" (installed from user/pkg/version "srAasovvDiQacRZ2mcId4RrOA8R2/ckp49i4j60000a2100yfwywgf/cl7ifcvv10000cz108r31chrd")
sf.soundflow.runCommand({
commandId: 'user:ckp49i4j60000a2100yfwywgf:cktcnf0ra000x9r109i8wen1u#cktcmsijz000i9r100tqfofgw',
props: {}
});
sf.file.open({
path: "/Users/shanemleonard/Desktop/music/PRO TOOLS/NEOTEK MIXING/Copy of SHANE MIX TEMPLATE/Copy of SHANE MIX TEMPLATE.ptx",
applicationPath: "/System/Library/CoreServices/Finder.app",
});
Links
User UID: iPlk3tDNNndwi8RxcF35OHBJhSw2
Feedback Key: sffeedback:iPlk3tDNNndwi8RxcF35OHBJhSw2:-NJfLTDsvlRTI_Ww2tY7
- OOwen Granich-Young @Owen_Granich_Young
Hi Shane,
Try this
function navigateTo(path) { //Get a reference to the focused window in the frontmost app: var win = sf.ui.frontmostApp.focusedWindow; //Open the Go to... sheet sf.keyboard.type({ text: '/' }); //Wait for the sheet to appear var sheet = win.sheets.first.elementWaitFor({ timeout: 500 }, 'Could not find "Go to" sheet in the Save/Open dialog').element; //It's a combo box on older OS'es, from 12.something it's a text field if (sheet.comboBoxes.first.exists) { sheet.comboBoxes.first.value.value = path; //Press OK sheet.buttons.whoseTitle.is('Go').first.elementClick({}, 'Could not click "Go"'); } else { //Newer OS. //TextField with AXConfirm var textField = sheet.textFields.first; if (!textField.exists) throw `Can't find text field`; textField.value.value = path; sheet.elementRaise(); textField.mouseClickElement({ relativePosition: { x: 5, y: 5 }, }); sf.keyboard.press({ keys: 'return' }); } //Wait for sheet to close win.sheets.first.elementWaitFor({ waitForNoElement: true, timeout: 2500 }, '"Go to" sheet didn\'t close in time'); } function importSession(selectedPath) { const numberOfTriesClickingOpen = 40; const timeToWaitBetweenOpenAttempts = 200; sf.ui.proTools.appActivateMainWindow(); // Open up Import Session Data sf.ui.proTools.menuClick({ menuPath: ['File', 'Import', 'Session Data...'] }); // Wait for window to open var importWin = sf.ui.proTools.windows.whoseTitle.is('Open').first; // Navigate to folder navigateTo(selectedPath); sf.wait({ intervalMs: 50, }); // Click Open //Press Open, try up to 10 times var openSuccess = false; for (var i = 0; i < numberOfTriesClickingOpen; i++) { try { if (importWin.invalidate().buttons.whoseTitle.is('Open').first.elementClick({ onError: 'Continue' }).success) { openSuccess = true; break; } } catch (err) { } sf.wait({ intervalMs: timeToWaitBetweenOpenAttempts }); } if (!openSuccess) throw "Could not click 'Open'"; // Wait for import window to close importWin.elementWaitFor({ waitForNoElement: true }); } function autoImportSessionData() { // Wait for Import Session Data window sf.ui.proTools.windows.whoseTitle.is("Import Session Data").first.elementWaitFor(); sf.keyboard.press({ keys: "cmd+a", }); sf.ui.proTools.windows.whoseTitle.is("Import Session Data").first.buttons.whoseTitle.is("OK").first.elementClick(); } function main() { sf.ui.proTools.appActivateMainWindow(); let templateLocation = `HOLD OPTION COPY YOUR FILE PATH HERE` importSession(templateLocation); autoImportSessionData(); } main();
On line 96 you define your session path.
- OOwen Granich-Young @Owen_Granich_Young
Here's another version that you don't have to manually copy your file name. Simply hold CMD and press your button the first time you run the script and it will ask you to navigate to the .ptx file you'd like it to open. The script should then remember that path until you Re-define it by holding command and pressing the button again.
function navigateTo(path) { //Get a reference to the focused window in the frontmost app: var win = sf.ui.frontmostApp.focusedWindow; //Open the Go to... sheet sf.keyboard.type({ text: '/' }); //Wait for the sheet to appear var sheet = win.sheets.first.elementWaitFor({ timeout: 500 }, 'Could not find "Go to" sheet in the Save/Open dialog').element; //It's a combo box on older OS'es, from 12.something it's a text field if (sheet.comboBoxes.first.exists) { sheet.comboBoxes.first.value.value = path; //Press OK sheet.buttons.whoseTitle.is('Go').first.elementClick({}, 'Could not click "Go"'); } else { //Newer OS. //TextField with AXConfirm var textField = sheet.textFields.first; if (!textField.exists) throw `Can't find text field`; textField.value.value = path; sheet.elementRaise(); textField.mouseClickElement({ relativePosition: { x: 5, y: 5 }, }); sf.keyboard.press({ keys: 'return' }); } //Wait for sheet to close win.sheets.first.elementWaitFor({ waitForNoElement: true, timeout: 2500 }, '"Go to" sheet didn\'t close in time'); } function importSession(selectedPath) { const numberOfTriesClickingOpen = 40; const timeToWaitBetweenOpenAttempts = 200; sf.ui.proTools.appActivateMainWindow(); // Open up Import Session Data sf.ui.proTools.menuClick({ menuPath: ['File', 'Import', 'Session Data...'] }); // Wait for window to open var importWin = sf.ui.proTools.windows.whoseTitle.is('Open').first; // Navigate to folder navigateTo(selectedPath); sf.wait({ intervalMs: 50, }); // Click Open //Press Open, try up to 10 times var openSuccess = false; for (var i = 0; i < numberOfTriesClickingOpen; i++) { try { if (importWin.invalidate().buttons.whoseTitle.is('Open').first.elementClick({ onError: 'Continue' }).success) { openSuccess = true; break; } } catch (err) { } sf.wait({ intervalMs: timeToWaitBetweenOpenAttempts }); } if (!openSuccess) throw "Could not click 'Open'"; // Wait for import window to close importWin.elementWaitFor({ waitForNoElement: true }); } function autoImportSessionData() { // Wait for Import Session Data window sf.ui.proTools.windows.whoseTitle.is("Import Session Data").first.elementWaitFor(); sf.keyboard.press({ keys: "cmd+a", }); sf.ui.proTools.windows.whoseTitle.is("Import Session Data").first.buttons.whoseTitle.is("OK").first.elementClick(); } function defineJson() { //save settings (goes in the script that saves the settings) let templateLocation = sf.interaction.selectFile({ prompt: "Select your Mix Template", }).path; let settingsToSave = { templateLocation, }; //this saves the settings file in the package directory of the command it is run from let jsonSavePath = sf.soundflow.thisPackage.getDirectory().path + '/savedSettings.json'; let success = sf.file.writeJson({ path: jsonSavePath, json: settingsToSave }).success; if (!success) throw `Error saving settings` } function main() { const modifierState = event.keyboardState const packageDirectoryPath = sf.soundflow.thisPackage.getDirectory().path; const jsonPath = `${packageDirectoryPath}/savedSettings.json`; if (modifierState.hasCommand || sf.file.directoryGetEntries({ path: packageDirectoryPath, searchPattern: 'savedSettings.json', isRecursive: true }) === undefined) { defineJson(); log(`Template Chosen`); } else { let recallSettings = sf.file.readJson({ path: jsonPath, }).json; let { templateLocation, } = recallSettings; importSession(templateLocation); autoImportSessionData(); } } main();
FWIW @kitch I couldn't get the 'if no .json path stored, run the defineJson' we worked on in the webinar working. If you or @Ryan_DeRemer have a chance to poke at it I'd love to know what I got wrong.
- OOwen Granich-Young @Owen_Granich_Young
@Shane_Leonard I just realized you're using a keyboard shortcut not a Streamdeck or Surface to trigger your script. You're probably better off with the first script then, as the hold CMD option within the script doesn't work with keyboard shortcuts.
- In reply toOwen_Granich_Young⬆:
Ryan DeRemer @Ryan_DeRemer
At a quick glance, I would probably just rework the
main()
function to this.function main() { const modifierState = event.keyboardState const packageDirectoryPath = sf.soundflow.thisPackage.getDirectory().path; const jsonPath = `${packageDirectoryPath}/savedSettings.json`; if (modifierState.hasCommand || sf.file.directoryGetEntries({ path: packageDirectoryPath, searchPattern: 'savedSettings.json', isRecursive: true }) === undefined) { defineJson(); log(`Template Chosen`); } else { let recallSettings = sf.file.readJson({ path: jsonPath, }).json; let { templateLocation, } = recallSettings; importSession(templateLocation); autoImportSessionData(); } }
- SShane Leonard @Shane_Leonard
Thanks - I'll try this.
- In reply toOwen_Granich_Young⬆:
Kitch Membery @Kitch2022-12-19 21:48:14.859Z
@Ryan_DeRemer beat me too it.
As a reusable function, here's my take.
function createNewJsonFile({ json, directory, fileName }) { const jsonFileDirectory = "~/Desktop/"; const jsonFileName = "myJson.json"; const jsonFileExists = sf.file.exists({ path: jsonFileDirectory + jsonFileName }).exists; log(jsonFileExists); // Remove this line Demo purpose only if (!jsonFileExists) { sf.file.writeJson({ path: directory + fileName, json, }); log(`A new JSON file was created in Path:${jsonFileDirectory + jsonFileName}`); // Remove this line Demo purpose only } } const jsonData = { testKey1: "Test Value", testKey2: "Test Value", }; createNewJsonFile({ json: jsonData, directory: "~/Desktop/", fileName: "myJson.json", });
This script will only create a new JSON file if one does not already exist. If one already exists it will not. (Note: Redundant second sentence for clarity only :-) ).
Kitch Membery @Kitch2022-12-19 21:58:19.224Z
Your idea at the SF Hangout was correct. I think in that moment I neglected to add the
.exists
that was needed as the return value from thesf.file.exists()
method.sf.file.exists({ path:"PATH HERE" }).exists;
:-)
Ryan DeRemer @Ryan_DeRemer
Yessir. The
.'return value'
part of the SF functions was a major source of headache for me in the beginning lol. Also there are a few (there are a lot but in the scope of the whole SF API it's just a few lol) functions that can be time-bombs depending on the use case. Personally, I'd rather build my own function that give return values ofnull
orundefined
(give me a nullish coalescing operator!!!) when something is not found. Or even an empty object or array. The errors in SF functions tend to have more to do with parts of the object being missing, due to a window not being there or whatever. So using aget
function that returns an array or null usually mitigates those types of errors. This is also why I try to keep folder structures simple, so I don't have to check does this folder exist, then does the next folder exist, etc etc.