No internet connection
  1. Home
  2. How to

Question about scripting for Finder App

By Philip weinrobe @Philip_weinrobe
    2024-04-07 17:08:53.914Z

    Hi Geniuses
    I'm trying to make a kind of complex finder script to help me collect approved mix and stem assets for delivery.
    My mix and stem folders often have many versions in them, and I need to extract only the approved versions for each song, collect them in a master folder, and send that to the client.
    Here's what i'm trying to do.

    Soundflow asks: "Select the Destination for Approved Mixes" -- I then select a folder
    Soundflow asks: "Select the first Song Folder" -- I then select the first songs folder in finder
    Soundflow asks: "What mix version is approved -- I then type in a number value (ex: 1.6)
    Soundflow then: Goes to the folder inside the "selected" folder called MIXES and copies all the mixes that contain the number 1.6
    Soundflow then: Pastes those files into the "Destination" folder
    Soundflow then: Goes to the folder inside the "selected song" folder called STEMS and copies any folders within that contain the number 1.6
    Soundflow asks: "Continue" or "Finished"
    If "Continue", it repeats process from step 2, allowing me to copy another Songs mixes and stems.

    If "Finished", zip the "Destination" folder


    Is the above process possible with soundflow??

    • 26 replies

    There are 26 replies. Estimated reading time: 40 minutes

    1. Kitch Membery @Kitch2024-04-09 00:03:59.994Z

      Hi @Philip_weinrobe

      I hope all is well with you!

      And... yes, this can be done. Try the following script out! :-)

      function moveApprovedMixes(destinationFolder) {
          // Get the song Folder
          const songFolder = sf.interaction.selectFolder({
              prompt: "Choose the First Song Folder",
          }).path;
      
          // Establish a path to the "MIXES" folder
          const firstSongMixesFolder = songFolder + "/STEMS/";
      
          // Prompt for the Approved Mix Version.
          const mixVersionNumber = sf.interaction.displayDialog({
              buttons: ["Ok", "Cancel"],
              cancelButton: "Cancel",
              prompt: "What mix version is approved",
              defaultAnswer: "",
              defaultButton: "Ok",
              onCancel: "Abort",
          }).text;
      
          // Filter the MIXES folder paths by the approved mix version number
          const approvedMixPaths = sf.file.directoryGetFiles({
              path: firstSongMixesFolder
          }).paths.filter(path => path.includes(mixVersionNumber));
      
      
          // Copy approved mixes to the Destination Folder
          approvedMixPaths.forEach(sourcePath => {
              const fileName = sourcePath.split("/").slice(-1).join("/");
              const destinationPath = `${destinationFolder}/${fileName}`;
      
              // Copy File to desination if the file does not exist their already.
              if (!sf.file.exists({ path: destinationPath }).exists) {
                  sf.file.copy({ sourcePath, destinationPath });
              }
          });
      
          // Ask if you'd like to Continue or if you are Finished
          const doZipFolder = sf.interaction.displayDialog({
              buttons: ["Continue", "Finished"],
              defaultButton: "Continue",
              prompt: `To move more files press "Continue", or press "Finished" to zip the Destination Folder`,
              onCancel: "Abort",
          }).button;
      
          if (doZipFolder === "Continue") {
              // Move more files
              moveApprovedMixes(destinationFolder);
          }
      }
      
      function zipDirectory(destinationFolder) {
          // Zip the files with logged progress
          sf.file.zipDirectory({
              sourcePath: destinationFolder,
              archivePath: `~/Desktop/Approved MIXES.zip`,
              onProgress: progressMessage => {
                  let percentageComplete = (progressMessage['progress'] * 100).toFixed(0);
                  log(`Processing:  ${percentageComplete} %`);
              },
              progressIntervalMs: 500
          });
          // Done
          log("Your approved mixes have been zipped");
      }
      
      function main() {
          // Get the Destination Folder
          const destinationFolder = sf.interaction.selectFolder({ prompt: "Choose Destination Folder", }).path;
      
          // Move Mixes
          moveApprovedMixes(destinationFolder)
      
          // Zip Destination Folder
          zipDirectory(destinationFolder);
      }
      
      main();
      

      I hope that helps. :-)

      1. PPhilip weinrobe @Philip_weinrobe
          2024-04-11 21:12:32.038Z

          ok holy moly this is incredible.
          it is SO CLOSE to perfect, and the issues were with my description of the functions.

          so your script as written grabs individual files perfectly...just did it for a whole record!
          and to do that i had to modify the location to be "_MIXES"

              // Establish a path to the "MIXES" folder
              const firstSongMixesFolder = songFolder + "/_MIXES/";
          

          however, i need the script, in addition to grabbing the mixes, to grab the Stem Folders.
          to do this, the script can go to the same root folder that contained the _MIXES sub folder, and there it will find an _STEMS sub folder.
          In that sub folder are more folders, and I would love for the script to get any folders with the same user-entered version number.
          I just tested it and it seems the script as written will only get files, not folders. Possible to just add this piece in?
          does that make sense? probably does since you were able to figure everything else out you genius you kitch!

          THANK YOU SO MUCH!
          philip

          1. PPhilip weinrobe @Philip_weinrobe
              2024-04-11 21:17:11.059Z

              oh, and one other small tweak:
              could the location of the archive (zip) be in the same root folder as the original destination folder? so it would essentially just land right next to the original destination folder in finder. and could naming convention be the name of the folder that the destination folder resides in?
              For example:
              Artist Folder: Philip Weinrobe
              Destination Folder: Philip Weinrobe/_MIX DELIVERY
              Archive Name: _MIX DELIVERY_Philip Weinrobe.zip
              Archive Location: Philip Weinrobe/

              1. In reply toPhilip_weinrobe:
                Kitch Membery @Kitch2024-04-11 22:10:27.541Z

                Nice! I knew there was more to it... I said to myself "Phillip... where are the stems???!!!" :-)

                To help with this can you take a screenshot of the session folder structure for a song? That include the Artist Folder, Destination Folder, _MIXES sub folder and _STEMS sub folder.

          2. P
            In reply toPhilip_weinrobe:
            Philip weinrobe @Philip_weinrobe
              2024-04-11 22:21:09.851Z
              1. Kitch Membery @Kitch2024-04-11 22:26:43.310Z

                Perfect! Will take a look at it in the next couple of days. :-)

                1. PPhilip weinrobe @Philip_weinrobe
                    2024-04-11 22:27:47.584Z

                    amazing thank you kitch!

                    1. Kitch Membery @Kitch2024-04-11 22:53:39.553Z

                      Is your folder structure always set up like this... If so I may be able to reduce the amount of mouse clicks involved.

                      If you were to select the song folders in the finder and then run the script I could make it ask for each song 'What is the approved mix number for "XX Song Name" from the selected folder paths'... the script could then find the "_MIX DELIVERY" folder without you needing for you to select it.

                      So the user steps would be

                      • Select all the song folders you'd like to collect mixes and stems from in the finder.
                      • Run the script
                      • Follow the prompts for each selected track and enter the approved mix numbers.
                      • Confirm if you'd like to Zip.

                      Happy to keep it the way it is though if that suits your flow. :-)

                      1. PPhilip weinrobe @Philip_weinrobe
                          2024-04-11 22:55:45.119Z

                          yes, always setup like this.
                          that is PERFECT. thank you for seeing me ;)

                          1. Kitch Membery @Kitch2024-04-11 23:20:09.846Z

                            While you're waiting...

                            @Chad and I got to talking a while back about a Secret Sonics podcast you were on, where you were talking about listening to short bursts of audio, we thought the concept was interesting and so I sent him this script. I thought you may dig it.

                            const burstLengthInMilliseconds = 80;
                            
                            /**
                             * Play a short burst of the Pro Tools timeline with burst length in milliseconds
                             * @param{Object} obj
                             * @param{Number} obj.burstLength
                             */
                            function playMicroBurstInMilliseconds({ burstLength }) {
                                const proTools = sf.ui.proTools;
                                const mainWindow = proTools.mainWindow;
                                mainWindow.invalidate();
                                const transportViewCluster = mainWindow.transportViewCluster;
                                const transportButtons = transportViewCluster.transportButtons;
                                const { playButton, stopButton } = transportButtons;
                            
                                // Ensure the Transport controls are visible
                                proTools.transportEnsureCluster();
                            
                                const isPlaying = proTools.invalidate().isPlaying;
                            
                                // If protools is playing, click stop button
                                if (isPlaying) stopButton.elementClick();
                            
                                // Click play button
                                playButton.elementClick();
                            
                                // Wait for burst length
                                sf.wait({ intervalMs: burstLength });
                            
                                // Click Stop button
                                stopButton.elementClick();
                            }
                            
                            playMicroBurstInMilliseconds({
                                burstLength: burstLengthInMilliseconds,
                            });
                            
                            1. PPhilip weinrobe @Philip_weinrobe
                                2024-04-11 23:22:08.371Z

                                omg i love this so much

                                1. PPhilip weinrobe @Philip_weinrobe
                                    2024-04-11 23:25:17.637Z

                                    holy moly this is extremely useful. you ROCK

                                    1. Kitch Membery @Kitch2024-04-11 23:29:05.641Z

                                      Awesome!

                        • In reply toPhilip_weinrobe:
                          Kitch Membery @Kitch2024-04-12 02:24:15.721Z

                          Hi @Philip_weinrobe,

                          Request for one more screenshot if I may. Of the content of the
                          "_MIX DELIVERY" folder content once all the files have been moved in there?

                          1. PPhilip weinrobe @Philip_weinrobe
                              2024-04-12 04:51:29.340Z

                              Hi Kitch
                              Check this out. In a perfect world the script could also create this folder.

                              All it would have to do is name the folder artistName_projectName_MIX DELIVERY
                              whereas projectName is the name of the folder with the songs in it
                              artist name is the name of the folder one up from projectName

                              Then inside the Mix Delivery folder is a folder for each of the songs

                              Inside each folder are the collected assets with the approved number

                              this make sense?

                              thanks for being so helpful!
                              Philip

                              1. Kitch Membery @Kitch2024-04-12 09:10:41.052Z

                                Hi @Philip_weinrobe,

                                Ok... Here we go :-)

                                If the parent folder of the numbered track names is the "Project Name", that would require updating the script to work for both scenarios, which I don't have time for right now. But the following script will work if the parent folder to the numbered songs folders is the "Artist Name" as in your original screenshots.

                                const MIXES_FOLDER_NAME = "_MIXES";
                                const STEMS_FOLDER_NAME = "_STEMS";
                                const MIXES_DELIVERY_FOLDER_NAME = "_MIX DELIVERY";
                                
                                function promptForApprovedVersionNumber(songName) {
                                    return sf.interaction.displayDialog({
                                        buttons: ["Ok", "Cancel"],
                                        cancelButton: "Cancel",
                                        prompt: `What mix version is approved for the following track...\n\n${songName}`,
                                        defaultAnswer: "",
                                        defaultButton: "Ok",
                                        onCancel: "Abort",
                                    }).text;
                                }
                                
                                function zipDirectoryWithProgress({ sourcePath, archivePath }) {
                                    sf.file.zipDirectory({
                                        sourcePath,
                                        archivePath,
                                        onProgress: progressMessage => {
                                            let percentageComplete = (progressMessage['progress'] * 100).toFixed(0);
                                            log(`Processing:  ${percentageComplete} %`);
                                        },
                                        progressIntervalMs: 500
                                    });
                                }
                                
                                function copyMixFiles({ mixesFolderPath, approvedVersionNumber, songDestinationFolder }) {
                                    // Filter the MIXES folder paths by the approved mix version number
                                    const approvedMixFilePaths = sf.file.directoryGetFiles({ path: mixesFolderPath }).paths
                                        .filter(path => path.includes(approvedVersionNumber));
                                
                                    approvedMixFilePaths.forEach(sourcePath => {
                                        const fileName = sourcePath.split("/").slice(-1).join("/");
                                        const destinationPath = `${songDestinationFolder}/${fileName}`;
                                
                                        // Copy File to destination if the file does not exist there already.
                                        if (!sf.file.exists({ path: destinationPath }).exists) {
                                            sf.file.copy({ sourcePath, destinationPath });
                                        }
                                    });
                                }
                                
                                function copyStemDirectories({ stemsFolderPath, approvedVersionNumber, songDestinationFolder }) {
                                    // Filter the STEM Folder paths by the approved version number
                                    const approvedStemFolderPaths = sf.file.directoryGetDirectories({ path: stemsFolderPath }).paths
                                        .filter(path => path.includes(approvedVersionNumber));
                                
                                    approvedStemFolderPaths.forEach(sourcePath => {
                                        const fileName = sourcePath.split("/").slice(-1).join("/");
                                        const destinationPath = `${songDestinationFolder}/${fileName}`;
                                
                                        // Copy the song Stems Folder if the folder does not exist there already.
                                        if (!sf.file.directoryExists({ path: destinationPath }).exists) {
                                            sf.file.copy({ sourcePath, destinationPath });
                                        }
                                    });
                                }
                                
                                function main() {
                                    // Get the selected folder paths from the finder and sort them
                                    const selectedFolderPaths = sf.ui.finder.selectedPaths.sort((a, b) => {
                                        const songNumberA = parseInt(a.match(/\/(\d{2}) /)[1], 10);
                                        const songNumberB = parseInt(b.match(/\/(\d{2}) /)[1], 10);
                                        return songNumberA - songNumberB;
                                    });
                                
                                    // Convert the first folder path into an array
                                    const folderPathArray = selectedFolderPaths[0].split("/").filter(Boolean);
                                
                                    const artistName = folderPathArray.slice(-2, -1).join("/");
                                
                                    const rootDestinationFolderPath = `${folderPathArray.slice(0, -1).join("/")}`;
                                
                                    selectedFolderPaths.forEach(sourcePath => {
                                        const mixesFolderPath = `${sourcePath}${MIXES_FOLDER_NAME}/`;
                                        const stemsFolderPath = `${sourcePath}${STEMS_FOLDER_NAME}/`;
                                
                                        // Get the song name based on the song folder name removing the nummbers and spaces at the beginning of the name.
                                        const songName = sourcePath.split("/").filter(Boolean)[sourcePath.split("/").filter(Boolean).length - 1].replace(/^(\d+\s+)/, '');
                                
                                        // Ask for approved mix number
                                        const approvedVersionNumber = promptForApprovedVersionNumber(songName);
                                
                                        // Create the "_MIX DELIVERY" directory if it does not exist
                                        sf.file.directoryCreate({ path: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}` });
                                
                                        // Make the song destination Folder path
                                        const songDestinationFolder = `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}/${songName}`;
                                
                                        // Create song destination Folder
                                        sf.file.directoryCreate({ path: songDestinationFolder });
                                
                                        // Copy Mixes to "_MIX DELIVERY/SONG NAME/"
                                        copyMixFiles({
                                            mixesFolderPath,
                                            approvedVersionNumber,
                                            songDestinationFolder
                                        });
                                
                                        // Copy Stem Folders to "_MIX DELIVERY/SONG NAME/"
                                        copyStemDirectories({
                                            stemsFolderPath,
                                            approvedVersionNumber,
                                            songDestinationFolder
                                        });
                                    });
                                
                                    // Ask to Zip? YES OR NO
                                    const doZipFolder = sf.interaction.displayDialog({
                                        buttons: ["No", "Yes"],
                                        defaultButton: "Yes",
                                        prompt: `Would you like to zip the Destination Folder?`,
                                        onCancel: "Abort",
                                    }).button;
                                
                                    if (doZipFolder === "Yes") {
                                        // Zip Destination Folder
                                        zipDirectoryWithProgress({
                                            sourcePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}`,
                                            archivePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}_${artistName}.zip`
                                        });
                                
                                        sf.interaction.displayDialog({ prompt: `The Mixes and Stems for "${artistName}" have been Zipped!` });
                                    }
                                
                                    // Open and select the Zip file in the finder.
                                    sf.file.open({ path: rootDestinationFolderPath });
                                    sf.ui.finder.appActivate();
                                }
                                
                                main();
                                

                                The steps are as follows.

                                • Select all the numbered song folders you'd like to collect mixes and stems for in the frontmost finder window.
                                • Run the script.
                                • Follow the prompts for each selected track and enter the approved mix numbers.
                                • If the "_MIX DELIVERY" folder does not exist it will be created.
                                • Confirm if you'd like to Zip the "_MIX DELIVERY" folder.
                                • If yes the Zip progress will be logged.
                                • If yes the Zip file will be in the same folder as the numbered song folders.
                                • The finder will be activated.

                                Note: If any MIX files or STEMS Folders exist in the "_MIX DELIVERY" folder already, they won't be overwritten.

                                I hope that helps! :-)

                                1. In reply toPhilip_weinrobe:
                                  Kitch Membery @Kitch2024-04-12 09:32:21.811Z
                                  1. PPhilip weinrobe @Philip_weinrobe
                                      2024-04-15 01:53:58.416Z

                                      this is fantastic.
                                      the only thing now is that it seems to prompt for each song, one at a time, only after moving the files.
                                      it takes a while to move the files sometimes, so i am sitting and babysitting the script...and then at the end choosing the zip yes/no.

                                      is it possible to have it loop through and ask for all the approved version numbers and the .zip yes/no right at the top, then go ahead and execute those decisions?

                                      is this possible?

                                      thank you!

                                      1. Kitch Membery @Kitch2024-04-15 17:26:03.937Z2024-04-15 18:22:39.302Z

                                        Ah yes... Having to babysit defeats the purpose.

                                        I've not had a chance to test this with large audio files but give this updated version a go. This one asks all the questions up front.

                                        const MIXES_FOLDER_NAME = "_MIXES";
                                        const STEMS_FOLDER_NAME = "_STEMS";
                                        const MIXES_DELIVERY_FOLDER_NAME = "_MIX DELIVERY";
                                        
                                        function promptForApprovedVersionNumber(songName) {
                                            return sf.interaction.displayDialog({
                                                buttons: ["Ok", "Cancel"],
                                                cancelButton: "Cancel",
                                                prompt: `What mix version is approved for the following track...\n\n${songName}`,
                                                defaultAnswer: "",
                                                defaultButton: "Ok",
                                                onCancel: "Abort",
                                            }).text;
                                        }
                                        
                                        function zipDirectoryWithProgress({ sourcePath, archivePath }) {
                                            sf.file.zipDirectory({
                                                sourcePath,
                                                archivePath,
                                                onProgress: progressMessage => {
                                                    let percentageComplete = (progressMessage['progress'] * 100).toFixed(0);
                                                    log(`Processing: ${percentageComplete} %`);
                                                },
                                                progressIntervalMs: 500
                                            });
                                        }
                                        
                                        function copyFiles({ songName, mixesFolderPath, stemsFolderPath, approvedVersionNumber, songDestinationFolder }) {
                                            log(`Copying Mixes for ${songName}`);
                                        
                                            // Create song destination Folder
                                            sf.file.directoryCreate({ path: songDestinationFolder });
                                        
                                            // Filter the MIXES folder paths by the approved mix version number
                                            const approvedMixFilePaths = sf.file.directoryGetFiles({ path: mixesFolderPath }).paths
                                                .filter(path => path.includes(approvedVersionNumber));
                                        
                                            approvedMixFilePaths.forEach(sourcePath => {
                                                const fileName = sourcePath.split("/").slice(-1).join("/");
                                                const destinationPath = `${songDestinationFolder}/${fileName}`;
                                        
                                                // Copy File to destination if the file does not exist there already.
                                                if (!sf.file.exists({ path: destinationPath }).exists) {
                                                    sf.file.copy({ sourcePath, destinationPath });
                                                }
                                            });
                                        
                                            // Filter the STEM Folder paths by the approved version number
                                            const approvedStemFolderPaths = sf.file.directoryGetDirectories({ path: stemsFolderPath }).paths
                                                .filter(path => path.includes(approvedVersionNumber));
                                        
                                            log(`Copying Stems for ${songName}`);
                                        
                                            approvedStemFolderPaths.forEach(sourcePath => {
                                                const fileName = sourcePath.split("/").slice(-1).join("/");
                                                const destinationPath = `${songDestinationFolder}/${fileName}`;
                                        
                                                // Copy the song Stems Folder if the folder does not exist there already.
                                                if (!sf.file.directoryExists({ path: destinationPath }).exists) {
                                                    sf.file.copy({ sourcePath, destinationPath });
                                                }
                                            });
                                        
                                            log(`Finished copying Mixes and Stems for ${songName}`);
                                        }
                                        
                                        function main() {
                                            // Get the selected folder paths from the finder and sort them
                                            const selectedFolderPaths = sf.ui.finder.selectedPaths.sort((a, b) => {
                                                const songNumberA = parseInt(a.match(/\/(\d{2}) /)[1], 10);
                                                const songNumberB = parseInt(b.match(/\/(\d{2}) /)[1], 10);
                                                return songNumberA - songNumberB;
                                            });
                                        
                                            // Convert the first folder path into an array
                                            const folderPathArray = selectedFolderPaths[0].split("/").filter(Boolean);
                                        
                                            const artistName = folderPathArray.slice(-2, -1).join("/");
                                        
                                            const rootDestinationFolderPath = `${folderPathArray.slice(0, -1).join("/")}`;
                                        
                                            const deliverablesToProcess = [];
                                        
                                            selectedFolderPaths.forEach(sourcePath => {
                                                const mixesFolderPath = `${sourcePath}${MIXES_FOLDER_NAME}/`;
                                                const stemsFolderPath = `${sourcePath}${STEMS_FOLDER_NAME}/`;
                                        
                                                // Get the song name based on the song folder name removing the numbers and spaces at the beginning of the name.
                                                const songName = sourcePath.split("/").filter(Boolean)[sourcePath.split("/").filter(Boolean).length - 1].replace(/^(\d+\s+)/, '');
                                        
                                                // Ask for approved mix number
                                                const approvedVersionNumber = promptForApprovedVersionNumber(songName);
                                        
                                                // Make the song destination Folder path
                                                const songDestinationFolder = `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}/${songName}`;
                                        
                                                // Copy Mixes to "_MIX DELIVERY/SONG NAME/"
                                                deliverablesToProcess.push({
                                                    songName,
                                                    mixesFolderPath,
                                                    stemsFolderPath,
                                                    approvedVersionNumber,
                                                    songDestinationFolder
                                                });
                                            });
                                        
                                            // Ask to Zip? YES OR NO
                                            const doZipFolder = sf.interaction.displayDialog({
                                                buttons: ["No", "Yes"],
                                                defaultButton: "Yes",
                                                prompt: `Would you like to zip the Destination Folder?`,
                                                onCancel: "Abort",
                                            }).button;
                                        
                                            // Create the "_MIX DELIVERY" directory if it does not exist
                                            sf.file.directoryCreate({ path: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}` });
                                        
                                            // Copy All Mixes
                                            deliverablesToProcess.forEach(song => {
                                                copyFiles({
                                                    songName: song.songName,
                                                    mixesFolderPath: song.mixesFolderPath,
                                                    stemsFolderPath: song.stemsFolderPath,
                                                    approvedVersionNumber: song.approvedVersionNumber,
                                                    songDestinationFolder: song.songDestinationFolder
                                                });
                                            });
                                        
                                            if (doZipFolder === "Yes") {
                                                // Zip Destination Folder
                                                zipDirectoryWithProgress({
                                                    sourcePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}`,
                                                    archivePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}_${artistName}.zip`
                                                });
                                        
                                                // Display message to say the files have been zipped.
                                                sf.interaction.displayDialog({
                                                    prompt: `The Mixes and Stems for "${artistName}" have been Zipped!`
                                                });
                                            }
                                        
                                            // Open and select the Zip file in the finder.
                                            sf.file.open({ path: rootDestinationFolderPath });
                                            sf.ui.finder.appActivate();
                                        }
                                        
                                        main();
                                        

                                        Rock on!

                                        1. PPhilip weinrobe @Philip_weinrobe
                                            2024-04-30 15:05:51.756Z

                                            hey kitch!
                                            been living with this script and it is amazing.
                                            two little tweaks would be incredible.

                                            1: the script currently fails if there is not an _STEMS folder. can we make it so the script keeps going if there is no _STEMS folder? Not every song gets stems...

                                            2: Currently the script requires the song folder to start with a number. Can we remove that necessity?

                                            Thank you!

                                            1. Kitch Membery @Kitch2024-05-06 17:42:10.474Z

                                              I have a busy week this week, but I'll see if I can make some time to implement these updates. :-)

                                              Will keep you posted, legend!

                                              1. In reply toPhilip_weinrobe:
                                                Kitch Membery @Kitch2024-05-07 07:44:08.742Z

                                                Try this version @Philip_weinrobe :-)

                                                const MIXES_FOLDER_NAME = "_MIXES";
                                                const STEMS_FOLDER_NAME = "_STEMS";
                                                const MIXES_DELIVERY_FOLDER_NAME = "_MIX DELIVERY";
                                                
                                                function promptForApprovedVersionNumber(songName) {
                                                    return sf.interaction.displayDialog({
                                                        buttons: ["Ok", "Cancel"],
                                                        cancelButton: "Cancel",
                                                        prompt: `What mix version is approved for the following track...\n\n${songName}`,
                                                        defaultAnswer: "",
                                                        defaultButton: "Ok",
                                                        onCancel: "Abort",
                                                    }).text;
                                                }
                                                
                                                function zipDirectoryWithProgress({ sourcePath, archivePath }) {
                                                    sf.file.zipDirectory({
                                                        sourcePath,
                                                        archivePath,
                                                        onProgress: progressMessage => {
                                                            let percentageComplete = (progressMessage['progress'] * 100).toFixed(0);
                                                            log(`Processing: ${percentageComplete} %`);
                                                        },
                                                        progressIntervalMs: 500
                                                    });
                                                }
                                                
                                                function copyFiles({ songName, mixesFolderPath, stemsFolderPath, approvedVersionNumber, songDestinationFolder }) {
                                                    log(`Copying Mixes for ${songName}`);
                                                
                                                    // Create song destination Folder
                                                    sf.file.directoryCreate({ path: songDestinationFolder });
                                                
                                                    // Filter the MIXES folder paths by the approved mix version number
                                                    const approvedMixFilePaths = sf.file.directoryGetFiles({ path: mixesFolderPath }).paths
                                                        .filter(path => path.includes(approvedVersionNumber));
                                                
                                                    approvedMixFilePaths.forEach(sourcePath => {
                                                        const fileName = sourcePath.split("/").slice(-1).join("/");
                                                        const destinationPath = `${songDestinationFolder}/${fileName}`;
                                                
                                                        // Copy File to destination if the file does not exist there already.
                                                        if (!sf.file.exists({ path: destinationPath }).exists) {
                                                            sf.file.copy({ sourcePath, destinationPath });
                                                        }
                                                    });
                                                
                                                    if (sf.file.directoryExists({ path: stemsFolderPath }).exists) {
                                                        // Filter the STEM Folder paths by the approved version number
                                                        const approvedStemFolderPaths = sf.file.directoryGetDirectories({ path: stemsFolderPath }).paths
                                                            .filter(path => path.includes(approvedVersionNumber));
                                                
                                                        log(`Copying Stems for ${songName}`);
                                                
                                                        approvedStemFolderPaths.forEach(sourcePath => {
                                                            const fileName = sourcePath.split("/").slice(-1).join("/");
                                                            const destinationPath = `${songDestinationFolder}/${fileName}`;
                                                
                                                            // Copy the song Stems Folder if the folder does not exist there already.
                                                            if (!sf.file.directoryExists({ path: destinationPath }).exists) {
                                                                sf.file.copy({ sourcePath, destinationPath });
                                                            }
                                                        });
                                                    }
                                                
                                                    log(`Finished copying Mixes and Stems for ${songName}`);
                                                }
                                                
                                                function main() {
                                                    // Get the selected folder paths from the finder and sort them
                                                    const selectedFolderPaths = sf.ui.finder.invalidate().selectedPaths;
                                                
                                                    const folderNames = selectedFolderPaths.map(path => path.split("/").filter(Boolean).slice(-1)[0])
                                                
                                                    function allStartWithNumber(arr) {
                                                        const regex = /^\d/;
                                                        return arr.every((str) => regex.test(str));
                                                    }
                                                    const foldersAreNumerical = allStartWithNumber(folderNames)
                                                
                                                    // Sort all Folders by number if they are numbered
                                                    if (foldersAreNumerical) {
                                                        selectedFolderPaths.sort((a, b) => {
                                                            const songNumberA = parseInt(a.match(/\/(\d{2}) /)[1], 10);
                                                            const songNumberB = parseInt(b.match(/\/(\d{2}) /)[1], 10);
                                                            return songNumberA - songNumberB;
                                                        });
                                                    }
                                                
                                                    // Convert the first folder path into an array
                                                    const folderPathArray = selectedFolderPaths[0].split("/").filter(Boolean);
                                                
                                                    const artistName = folderPathArray.slice(-2, -1).join("/");
                                                
                                                    const rootDestinationFolderPath = `${folderPathArray.slice(0, -1).join("/")}`;
                                                
                                                    const deliverablesToProcess = [];
                                                
                                                    selectedFolderPaths.forEach(sourcePath => {
                                                        const mixesFolderPath = `${sourcePath}${MIXES_FOLDER_NAME}/`;
                                                        const stemsFolderPath = `${sourcePath}${STEMS_FOLDER_NAME}/`;
                                                
                                                        // Get the song name based on the song folder name removing the numbers and spaces at the beginning of the name.
                                                        const songName = sourcePath.split("/").filter(Boolean)[sourcePath.split("/").filter(Boolean).length - 1].replace(/^(\d+\s+)/, '');
                                                
                                                        // Ask for approved mix number
                                                        const approvedVersionNumber = promptForApprovedVersionNumber(songName);
                                                
                                                        // Make the song destination Folder path
                                                        const songDestinationFolder = `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}/${songName}`;
                                                
                                                        // Copy Mixes to "_MIX DELIVERY/SONG NAME/"
                                                        deliverablesToProcess.push({
                                                            songName,
                                                            mixesFolderPath,
                                                            stemsFolderPath,
                                                            approvedVersionNumber,
                                                            songDestinationFolder
                                                        });
                                                    });
                                                
                                                    // Ask to Zip? YES OR NO
                                                    const doZipFolder = sf.interaction.displayDialog({
                                                        buttons: ["No", "Yes"],
                                                        defaultButton: "Yes",
                                                        prompt: `Would you like to zip the Destination Folder?`,
                                                        onCancel: "Abort",
                                                    }).button;
                                                
                                                    // Create the "_MIX DELIVERY" directory if it does not exist
                                                    sf.file.directoryCreate({ path: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}` });
                                                
                                                    // Copy All Mixes
                                                    deliverablesToProcess.forEach(song => {
                                                        copyFiles({
                                                            songName: song.songName,
                                                            mixesFolderPath: song.mixesFolderPath,
                                                            stemsFolderPath: song.stemsFolderPath,
                                                            approvedVersionNumber: song.approvedVersionNumber,
                                                            songDestinationFolder: song.songDestinationFolder
                                                        });
                                                    });
                                                
                                                    if (doZipFolder === "Yes") {
                                                        // Zip Destination Folder
                                                        zipDirectoryWithProgress({
                                                            sourcePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}`,
                                                            archivePath: `${rootDestinationFolderPath}/${MIXES_DELIVERY_FOLDER_NAME}_${artistName}.zip`
                                                        });
                                                
                                                        // Display message to say the files have been zipped.
                                                        sf.interaction.displayDialog({
                                                            prompt: `The Mixes and Stems for "${artistName}" have been Zipped!`
                                                        });
                                                    }
                                                
                                                    // Open and select the Zip file in the finder.
                                                    sf.file.open({ path: rootDestinationFolderPath });
                                                    sf.ui.finder.appActivate();
                                                }
                                                
                                                main();
                                                
                                                1. PPhilip weinrobe @Philip_weinrobe
                                                    2024-05-17 11:36:17.012Z

                                                    kitch, this is truly amazing. thank you.
                                                    a significant improvement to my life on an almost daily basis.
                                                    not subtle!

                                                    1. Kitch Membery @Kitch2024-05-17 18:59:06.643Z

                                                      @Philip_weinrobe,

                                                      You're welcome, that's what SoundFlow's all about.

                                                      More time to focus on the fun stuff! :-)

                                                      Have a great weekend, when you get there!