No internet connection
  1. Home
  2. Macro and Script Help

Creating an "Import Mix Template" macro.

By Shane Leonard @Shane_Leonard
    2022-12-19 17:06:29.599Z

    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

    Feedback ZIP

    • 8 replies
    1. O

      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.

      1. OOwen Granich-Young @Owen_Granich_Young
          2022-12-19 19:52:25.647Z2022-12-19 21:51:21.817Z

          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.

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

            1. Ryan DeRemer @Ryan_DeRemer
                2022-12-19 21:19:44.842Z

                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();
                    }
                }
                
                1. SShane Leonard @Shane_Leonard
                    2022-12-19 21:46:26.928Z

                    Thanks - I'll try this.

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

                    1. Kitch Membery @Kitch2022-12-19 21:58:19.224Z

                      @Ryan_DeRemer,

                      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 the sf.file.exists() method.

                      sf.file.exists({ path:"PATH HERE" }).exists;
                      

                      :-)

                      1. Ryan DeRemer @Ryan_DeRemer
                          2022-12-19 22:06:57.105Z

                          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 of null or undefined (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 a get 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.