No internet connection
  1. Home
  2. Support

screenshot opening plugin

By Oli Jacobs @Oli_Jacobs
    2025-06-02 17:41:01.781Z

    Title

    screenshot opening plugin

    What do you expect to happen when you run the script/macro?

    save a screenshot of the open plugin to the bounced files in pro tools

    Are you seeing an error?

    api doesnt seem to be communicating properly e.g. object has no method 'screenshot'

    What happens when you run this script?

    I am experiencing significant issues with the SoundFlow scripting API (version 5.10.5) on my macOS system. Several core API functions, object methods, and properties appear to be missing, undefined, or are not behaving as expected in my custom JavaScript scripts. These problems persist even after a fresh reinstallation of SoundFlow and when testing with brand new, minimal scripts.

    I have been working on developing a script to capture a screenshot of an open Pro Tools plugin, name it based on session/track information, and save it. Through systematic debugging of this script, a number of fundamental API issues have been identified.

    While some parts of the API work correctly (e.g., sf.interaction.notify has been crucial for debugging, sf.ui.proTools.mainWindow.sessionPath correctly returns the session path as a direct string, and sf.ui.proTools.appActivate seems to function), many others do not.

    Here is a summary of the specific API issues encountered:

    sf.log() is undefined: Basic logging to the SoundFlow Log window is not possible.
    sf.ui.proTools.sessionName returns undefined: The script is unable to get the session name directly via this API call. (We worked around this by deriving it from the sessionPath).
    sf.interaction.showDialog() method is missing: Attempting to call it results in a TypeError: Object has no method 'showDialog'. This prevents prompting the user for input.
    sf.file.ensureDirectoryExists() method is missing: Attempting to call it results in a TypeError: Object has no method 'ensureDirectoryExists'.
    Plugin Window UI Element Properties are undefined: When a Pro Tools plugin window (e.g., 'Plug-in: Ozone 11 Maximizer') is identified in the sf.ui.proTools.floatingWindows array (which correctly lists it as the only floating window), its key properties are reported as undefined when queried via a script using sf.interaction.notify:
    isActive: undefined
    isFloating: undefined
    isVisible: undefined
    isModal: undefined
    The window frame property also appears to be invalid, leading to Size: undefinedxundefined.
    SFUIElement.screenshot() method is missing: When attempting to call .screenshot() on the identified plugin window object, it results in a TypeError: Object has no method 'screenshot'.
    Track Auto-Detection Issues: Attempts to get sf.ui.proTools.selectedTrackNames after sf.ui.proTools.mainWindow.elementClick() frequently result in a caught error "Error: ClickButtonAction". Backend logs have shown Logging error in action (01) ClickButtonAction: Error invoking AXElement: kAXErrorActionUnsupported related to this.
    Troubleshooting Steps Taken:

    A full reinstallation of SoundFlow (downloaded fresh, old app deleted).
    Testing the problematic API calls in brand new, minimal script files.
    Thoroughly checking and confirming macOS Permissions for SoundFlow in System Settings, including Accessibility, Screen Recording, Automation, and Full Disk Access (Full Disk Access was initially off but was enabled; this did not resolve these API issues).
    These API deficiencies make it impossible to develop or reliably run custom scripts that depend on these standard functionalities. The scripting environment seems to be missing key parts of its documented API surface.

    Could you please investigate why these API components might be unavailable or malfunctioning in my SoundFlow 5.10.5 installation? I can provide the specific test scripts used and detailed notification logs/screenshots if needed.

    Thank you for your time and assistance.

    How were you running this script?

    I clicked the "Run Script" or "Run Macro" button in SoundFlow

    How important is this issue to you?

    4

    Details

    {
        "inputExpected": "save a screenshot of the open plugin to the bounced files in pro tools",
        "inputIsError": true,
        "inputError": "api doesnt seem to be communicating properly e.g. object has no method 'screenshot'",
        "inputWhatHappens": "I am experiencing significant issues with the SoundFlow scripting API (version 5.10.5) on my macOS system. Several core API functions, object methods, and properties appear to be missing, undefined, or are not behaving as expected in my custom JavaScript scripts. These problems persist even after a fresh reinstallation of SoundFlow and when testing with brand new, minimal scripts.\n\nI have been working on developing a script to capture a screenshot of an open Pro Tools plugin, name it based on session/track information, and save it. Through systematic debugging of this script, a number of fundamental API issues have been identified.\n\nWhile some parts of the API work correctly (e.g., sf.interaction.notify has been crucial for debugging, sf.ui.proTools.mainWindow.sessionPath correctly returns the session path as a direct string, and sf.ui.proTools.appActivate seems to function), many others do not.\n\nHere is a summary of the specific API issues encountered:\n\nsf.log() is undefined: Basic logging to the SoundFlow Log window is not possible.\nsf.ui.proTools.sessionName returns undefined: The script is unable to get the session name directly via this API call. (We worked around this by deriving it from the sessionPath).\nsf.interaction.showDialog() method is missing: Attempting to call it results in a TypeError: Object has no method 'showDialog'. This prevents prompting the user for input.\nsf.file.ensureDirectoryExists() method is missing: Attempting to call it results in a TypeError: Object has no method 'ensureDirectoryExists'.\nPlugin Window UI Element Properties are undefined: When a Pro Tools plugin window (e.g., 'Plug-in: Ozone 11 Maximizer') is identified in the sf.ui.proTools.floatingWindows array (which correctly lists it as the only floating window), its key properties are reported as undefined when queried via a script using sf.interaction.notify:\nisActive: undefined\nisFloating: undefined\nisVisible: undefined\nisModal: undefined\nThe window frame property also appears to be invalid, leading to Size: undefinedxundefined.\nSFUIElement.screenshot() method is missing: When attempting to call .screenshot() on the identified plugin window object, it results in a TypeError: Object has no method 'screenshot'.\nTrack Auto-Detection Issues: Attempts to get sf.ui.proTools.selectedTrackNames after sf.ui.proTools.mainWindow.elementClick() frequently result in a caught error \"Error: ClickButtonAction\". Backend logs have shown Logging error in action (01) ClickButtonAction: Error invoking AXElement: kAXErrorActionUnsupported related to this.\nTroubleshooting Steps Taken:\n\nA full reinstallation of SoundFlow (downloaded fresh, old app deleted).\nTesting the problematic API calls in brand new, minimal script files.\nThoroughly checking and confirming macOS Permissions for SoundFlow in System Settings, including Accessibility, Screen Recording, Automation, and Full Disk Access (Full Disk Access was initially off but was enabled; this did not resolve these API issues).\nThese API deficiencies make it impossible to develop or reliably run custom scripts that depend on these standard functionalities. The scripting environment seems to be missing key parts of its documented API surface.\n\nCould you please investigate why these API components might be unavailable or malfunctioning in my SoundFlow 5.10.5 installation? I can provide the specific test scripts used and detailed notification logs/screenshots if needed.\n\nThank you for your time and assistance.",
        "inputHowRun": {
            "key": "-MpfwYA4I6GGlXgvp5j1",
            "title": "I clicked the \"Run Script\" or \"Run Macro\" button in SoundFlow"
        },
        "inputImportance": 4,
        "inputTitle": "screenshot opening plugin\n"
    }

    Source

    /**
     * @param {string} userTrackName - Optional: Manually enter track name.
     */
    function capturePluginScreenshot({ userTrackName }) {
        sf.interaction.notify({ title: "Script Action", message: "Capture Plugin Screenshot script started." });
    
        try {
            sf.ui.proTools.appActivate();
            sf.wait({ intervalMs: 300 });
        } catch (e) {
            sf.interaction.notify({ title: "Script Error", message: "Error activating Pro Tools: " + e.toString() });
            return;
        }
    
        var ptWindow = sf.ui.proTools.mainWindow;
        var sessionPathString = sf.ui.proTools.mainWindow.sessionPath;
        var sessionNameBase;
    
        // Validate Session Path
        if (sessionPathString && typeof sessionPathString === 'string' && sessionPathString.trim() !== "") {
            sessionPathString = sessionPathString.trim();
        } else {
            sf.interaction.notify({ title: "Script Error", message: "Could not retrieve valid Pro Tools session path." }); return;
        }
        sf.interaction.notify({ title: "Script Info", message: "Session Path: " + sessionPathString });
        
        // Get/Derive Session Name
        var sessionNameApiResult = sf.ui.proTools.sessionName;
        if (sessionNameApiResult && typeof sessionNameApiResult === 'string' && sessionNameApiResult.trim() !== "") {
            let trimmedApiName = sessionNameApiResult.trim();
            sessionNameBase = trimmedApiName.toLowerCase().endsWith('.ptx') ? trimmedApiName.substring(0, trimmedApiName.length - 4) : trimmedApiName;
        } else {
            sf.interaction.notify({ title: "Script Warning", message: "Session Name from API invalid. Deriving from path." });
            if (sessionPathString) {
                let pathParts = sessionPathString.split('/');
                let derivedName = pathParts[pathParts.length - 1];
                if (derivedName && derivedName.trim() !== "") {
                    sessionNameBase = derivedName.toLowerCase().endsWith('.ptx') ? derivedName.substring(0, derivedName.length - 4) : derivedName;
                }
            }
        }
        if (!sessionNameBase || sessionNameBase.trim() === "") {
            sf.interaction.notify({ title: "Script Error", message: "Could not determine valid session name." }); return;
        }
        sf.interaction.notify({ title: "Script Info", message: "Using Session Name for file: " + sessionNameBase });
    
        // Get Track Name (with placeholder)
        var selectedTrackName;
        var trackIssue = false;
        if (userTrackName && userTrackName.trim() !== "") {
            selectedTrackName = userTrackName.trim();
        } else {
            try {
                if (ptWindow.invalidate().exists) { ptWindow.elementClick(); sf.wait({ intervalMs: 100 }); }
                var trackNames = sf.ui.proTools.selectedTrackNames;
                if (trackNames && trackNames.length > 0 && trackNames[0]) { selectedTrackName = trackNames[0]; } 
                else { throw "No track selected/found via API."; }
            } catch (e) {
                selectedTrackName = "TRACK_NAME_UNKNOWN"; trackIssue = true;
            }
        }
        sf.interaction.notify({ title: "Script Info", message: "Track Name: " + selectedTrackName + (trackIssue ? " (placeholder/error)" : "") });
    
        // Date and Filename
        var date = new Date();
        var year = date.getFullYear();
        var month = ('0' + (date.getMonth() + 1)).slice(-2);
        var day = ('0' + date.getDate()).slice(-2);
        var formattedDate = `${year}${month}${day}`;
        var fileName = `${sessionNameBase}_${selectedTrackName}_${formattedDate}.png`;
        fileName = fileName.replace(/[\/\\?%*:|"<>]/g, '-');
        sf.interaction.notify({ title: "Script Info", message: "Filename: " + fileName });
    
        // Save Folder Path
        var saveFolderPath;
        let L = sessionNameBase.length;
        if (sessionPathString.toLowerCase().endsWith('.ptx')) { // Path to .ptx file
            saveFolderPath = sessionPathString.substring(0, sessionPathString.lastIndexOf('/'));
        } else if (sessionPathString.length > L && sessionPathString.endsWith(sessionNameBase)) { // Path is /.../ParentFolder/SessionName (where SessionName doesn't have .ptx)
            saveFolderPath = sessionPathString.substring(0, sessionPathString.length - L);
            if (saveFolderPath.endsWith('/')) {
               saveFolderPath = saveFolderPath.substring(0, saveFolderPath.length - 1);
            }
        } else { // Fallback: assume sessionPathString is the folder
            saveFolderPath = sessionPathString;
        }
        var fullPath = `${saveFolderPath}/${fileName}`;
        sf.interaction.notify({ title: "Script Info", message: "Attempting to save to: " + fullPath });
    
        // ---- MODIFIED PLUGIN WINDOW DETECTION ----
        var pluginWindowToUse;
        try {
            sf.interaction.notify({ title: "Debug Info", message: "Listing floating windows (lenient check)..." });
            var allFloatingWindows = sf.ui.proTools.floatingWindows;
    
            if (!allFloatingWindows || allFloatingWindows.length === 0) {
                sf.interaction.notify({ title: "Script Warning", message: "No floating windows reported by SoundFlow at all." });
                return;
            }
            sf.interaction.notify({ title: "Debug Info", message: "Total floating windows (unfiltered): " + allFloatingWindows.length });
    
            // Ultra-lenient check: If exactly one floating window exists and its title contains "Plug-in" (or is your specific plugin)
            if (allFloatingWindows.length === 1) {
                let تنهاپنجره = allFloatingWindows[0]; // "تنهاپنجره" means "only window" in Persian, just for fun here
                let winTitle = تنهاپنجره.title.value || "";
                sf.interaction.notify({ title: "Debug Info", message: "Single floating window found. Title: '" + winTitle + "'" });
                // Check if title contains "Plug-in" or specific known plugin titles if needed
                if (winTitle.includes("Plug-in:") || winTitle.includes(ptWindow.focusedWindow.title.value)) { // Check against focused window title as a guess
                     pluginWindowToUse = تنهاپنجره;
                     sf.interaction.notify({ title: "Script Info", message: "Using the single floating window based on title: '" + winTitle + "'" });
                } else {
                     sf.interaction.notify({ title: "Script Warning", message: "Single floating window title doesn't clearly indicate it's a plugin. Title: '" + winTitle + "'" });
                     return;
                }
            } else {
                // If more than one, try to find one that's not Edit/Mix and seems plausible (very heuristic)
                // This part is risky if properties are undefined.
                // We'll be very conservative: if not exactly one promising window, we fail.
                sf.interaction.notify({ title: "Script Warning", message: "Expected 1 plugin-like floating window, found " + allFloatingWindows.length + ". Cannot reliably select." });
                // You could add more logic here to loop through allFloatingWindows and pick one based on title if needed,
                // but it gets complex if their properties (isActive, isVisible) are unreliable.
                return;
            }
            
            if (!pluginWindowToUse || !pluginWindowToUse.exists) { // Check .exists just in case
                sf.interaction.notify({ title: "Script Warning", message: "Could not identify a plugin window with lenient check." });
                return;
            }
            
            pluginWindowToUse.elementRaise();
            sf.wait({ intervalMs: 300 });
    
            sf.interaction.notify({ title: "Script Action", message: "Attempting to capture and save screenshot for window: " + (pluginWindowToUse.title.value || "Untitled") });
            var screenshotResult = pluginWindowToUse.screenshot(); // This might fail if API is broken
            screenshotResult.saveTo({ path: fullPath, overwrite: true }); // This might also fail
            sf.interaction.notify({ title: "Screenshot Saved!", message: `Saved: ${fileName}\nIn: ${saveFolderPath}` });
    
        } catch (e) {
            sf.interaction.notify({ title: "Script Error", message: "Error during plugin window detection or screenshot: " + e.toString() });
        }
    }
    
    capturePluginScreenshot({});
    

    Links

    User UID: 5IgHNCmOPzfvLcfd9GtbpA0S2OC2

    Feedback Key: sffeedback:5IgHNCmOPzfvLcfd9GtbpA0S2OC2:-ORlis7wtZdcbCVbEuZU

    Feedback ZIP: /ZUTkJOwy5dbu2T+zpY/4D5QdT8k5jMhvREksOh1Ck2JVuBwk8ehSmaoCBo5OUsRBFznnAspbdvE21KoXNXMQHW/QCXOxBNTVUfmnE6XOSVw4+HRX6h6v5TkuTXSGNOE/dsaTjdCWdXZSU/cyKnUlj0XrEXlsOfPD0MBCN5eQyXhLoZij2y4qsnlboMqo1VDE55LnOot0DHduqhIrtMrzlxVgkAH+txbvVjRtuCYm5g1njAAqXa5CmYqH8pOvdNtanrozrXFjoZL28p9dbblAtx/o40S5d+3+w+SkV7wyyJ8m95zMYSLcsvkxaacwmA2IA3tXqTFeIE+8gGfn7ZwtNHJqZoCjJgutb0WQYROew4=

    • 7 replies
    1. Kitch Membery @Kitch2025-06-02 19:52:48.002Z

      Hi @Oli_Jacobs

      Forgive me if I'm wrong, but the script you've shared appears to contain content that may have been written by ChatGPT or some other AI platform. I say this, as there are multiple instances of properties that are not available to SoundFlow and Methods that are not a part of the SoundFlow API. There are also issues with the JSDoc documentation.

      To get better help, we typically recommend getting script help by following this guide, as it asks you questions that are designed to make it easier for the community to help you:
      https://soundflow.org/docs/help#script-help

      Thanks in advance.

      1. OOli Jacobs @Oli_Jacobs
          2025-06-03 08:28:49.295Z

          Hello, even basic testing shows that some scripts are unavailable:

          // Test sf.log availability
          sf.interaction.notify({
          title: "sf.log Test",
          message: "Type of sf.log: " + typeof sf.log
          });
          // On my system, this notification shows: "Type of sf.log: undefined"
          According to general API documentation (including the global API page I found at https://soundflow.org/docs/api/globals), sf.log is expected to be a function.

          The aim of the script is to screenshot the open plugin and save it to the bounced files part of the session, naming the screenshot with the plugin name, track name and session name. the idea being to quickly make a screenshot of a limiter or other plugin when sending files. E.g. sending the non limited mix to the mastering engineer.

          1. Kitch Membery @Kitch2025-06-03 21:13:19.349Z

            Hi @Oli_Jacobs

            Yes sf.log is a method, you could say a simplified version of sf.interaction.notify. You can use it in your scripts like this.

            Create a new script and add this code.

            log("This text will be logged")
            

            Run the script.

            "This text will be logged" will appear in the top right of your screen.


            Regarding the workflow you're trying to achieve, that's a great idea. I'll take a look in the next couple of days and see if I can create a script to take a screenshot of the frontmost plugin window with its destination set to the Bounce Folder. :-)

        • In reply toOli_Jacobs:
          Kitch Membery @Kitch2025-06-02 20:13:24.685Z

          Hi @Oli_Jacobs

          Actually, it looks as though you did use the Scrip Help Workflow.

          It may be best if you could, in point form, provide a list of manual steps that you'd take to perform the workflow, that way we can see if what you're after is possible. :-)

          1. In reply toOli_Jacobs:
            Kitch Membery @Kitch2025-06-03 23:44:20.367Z

            Hi @Oli_Jacobs

            Here's a script that should do what you are after.

            Please note that this will require approving some system permissions (but just the first time). Also, note that the plugin window must be frontmost and fully visible on your screen for the screen capture to work.

            sf.ui.proTools.mainWindow.invalidate();
            sf.ui.proTools.appActivate();
            
            const sessionPath = sf.ui.proTools.mainWindow.sessionPath;
            const sessionName = sessionPath.split('/').slice(-1)[0].split('.').slice(0, -1).join('.');
            const sessionDirectory = sessionPath.split('/').slice(0, -1).join('/');
            const bouncedFilesDirectory = `${sessionDirectory}/Bounced Files`;
            
            const frontmostPluginWindow = sf.ui.proTools.invalidate().pluginWindow;
            const pluginName = frontmostPluginWindow.title.value.replace("Plug-in: ", "");
            const associatedTrackName = frontmostPluginWindow.getFirstWithTitle("Track Selector").value.value;
            const { x, y, h, w } = frontmostPluginWindow.frame;
            
            const destinationFilePath = `${bouncedFilesDirectory}/${pluginName} - ${associatedTrackName} - ${sessionName}.png`;
            
            sf.system.exec({
                commandLine: `eval $(osascript -e '
            tell application "System Events"
                tell application process "Pro Tools"
                    set winBounds to bounds of front window
                    set {x1, y1, x2, y2} to winBounds
                    set w to x2 - x1
                    set h to y2 - y1
                    return "x=" & x1 & " y=" & y1 & " w=" & w & " h=" & h
                end tell
            end tell') && \
            screencapture -R"${x},${y},${w},${h}" "${destinationFilePath}"`
            });
            
            // This line will display the file in the finder.
            sf.system.exec({ commandLine: `open -R "${destinationFilePath}"` });
            

            Your mastering engineer is going to love you!

            1. OOli Jacobs @Oli_Jacobs
                2025-06-04 11:40:24.714Z

                amazing this works great thank you!

              • In reply toOli_Jacobs:
                Kitch Membery @Kitch2025-06-03 23:47:01.264Z

                Hi @Oli_Jacobs

                On a side note. I'd be more than happy to jump on a 1:1 Zoom call with you sometime to give you a SoundFlow overview. If that's something you'd be interested in, please reach out via email to support@soundflow.org, and we can set up a time :-)