My workflow involves duplicating project files in Finder and incrementing the number in the name each time there is a significant new version. This workflow is the same regardless of whether the extension is .ptx .mp4 .prproj etc
Example filenames
ProjectName-Mix-v1.ptx
ProjectName-Mix-v2.ptx
My script works OK so far, but it gives errors sometimes. Specifically sometimes it doesn't find the "Paste" menu item, and sometimes the script seems to happen too quickly and Finder gets the numbers wrong, or opens the old file instead of the new one. I have some manual "waits" in there but I was wondering if there was a better, more elegant and robust way of doing it.
Also I'm looking for a way to take my original file, and move it into a folder called "Old", and then to re-select the newly incremented file and open it (end of script).
And lastly currently it doesn't work if the file does not yet have a version number; not sure if there's a way around that.
Here's the script so far.
// Get name of selected file
var sourceFilePath = sf.ui.finder.firstSelectedPath;
var sourceFileName = sourceFilePath.replace(/^.*[\\\/]/, '');
var sourceFileNameQuoted = 'Copy “' + sourceFileName + '”';
var sourceFileNameNoExtension = sourceFileName.replace(/\.[^/.]+$/, "");
//Get and Increment Last Number
function getAndIncrementLastNumber(str) {
return str.replace(/\d+$/, function (s) {
return ++s;
});
}
var destinationFileName = getAndIncrementLastNumber(sourceFileNameNoExtension);
// Copy selected file
sf.ui.finder.menuClick({
menuPath: ["Edit",sourceFileNameQuoted],
});
sf.wait({
intervalMs: 300,
});
// Paste file
sf.ui.finder.menuClick({
menuPath: ["Edit","Paste Item"],
});
sf.wait({
intervalMs: 300,
});
// Rename
sf.ui.frontmostApp.menuClick({
menuPath: ["File","Rename"],
});
sf.keyboard.type({
text: destinationFileName,
});
sf.keyboard.press({
keys: "return",
});
// Open new file with default application
sf.ui.finder.menuClick({
menuPath: ["File","Open"],
});
// Move original source file to folder called "Old" (in the background)
// Create folder if it doesn't exist
Linked from:
- Chris Shaw @Chris_Shaw2021-02-25 01:20:56.309Z2021-02-25 15:44:15.396Z
The key here is to use the sf.ui.file' actions to move and copy files. This way you're not relying on finder clicks.
here you go:
// Get paths and names of selected file var fullPath = decodeURIComponent(sf.appleScript.finder.selection.getItem(0).asItem.path); var directory = fullPath.split('/').slice(0, -1).join('/'); var filename = fullPath.split('/').slice(-1)[0]; var filenameWithoutExtension = filename.split('.').slice(0, -1).join('.'); var extension = filename.split('.').slice(-1)[0]; //Create a new name (get the name and suffixed number - if your session name was 'My Session 42' then save 'My Session ' in grps[1] and '42' in grps[2]) var grps = filenameWithoutExtension.match(/^(.+?)(\d+)$/); //Construct a new name based on the prefix and the number (+1), followed by the '.ptx' suffix. If there was no number, just add ' 2' // This next line will add a suffix if no number is found at end of file (in this case " - V1") var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? ' - V1' : ((Number(grps[2]) + 1) + ''); var newFile = (grps ? grps[1] : (filenameWithoutExtension)) + newNumber + '.ptx'; // Save copy of session with new name sf.file.copy({ sourcePath: fullPath, destinationPath: newFile, }); // Move original source file to folder called "Old" (in the background) // Create folder if it doesn't exist // This will create 'Old Sessions' if it doesn't exist. sf.file.directoryCreate({ path: directory + '/' + "Old Sessions", }); sf.file.move({ sourcePath: fullPath, destinationPath: directory + '/' + "Old Sessions/" + filename, }); // Open new file sf.file.open({ path: newFile, }); ``
Dustin Harris @Dustin_Harris
Bah you beat me to it Chris! (and with way more elegant code!)🤟
Chris Shaw @Chris_Shaw2021-02-25 02:04:59.819Z
It's elegant because it's mostly @chrscheuer 's code🙂…
//CS//
- In reply toDustin_Harris⬆:
Chris Shaw @Chris_Shaw2021-02-25 02:13:24.754Z
That first chunk of code that splits the file path is in my code library. So useful.
- AAndrew Sherman @Andrew_Sherman
How do you manage / save items in your code library? What do you use?
Dustin Harris @Dustin_Harris
My ‘library’ is just a package that I’ve called ‘*Library’, which contains a bunch of scripts that have functions I reuse often, so instead of rewriting the same code over and over, I copy it from my ‘library’ script and paste it into the script I’m working on :)
- AAndrew Sherman @Andrew_Sherman
That one's going into the Ideas forum...
- In reply toChris_Shaw⬆:AAndrew Sherman @Andrew_Sherman
It looks like I need to change a permission on my hard drive for this to work, I'm getting this error when i try to run the command: IOException in file.copy: You can’t save the file “VideoTest-01.mov” because the volume “Macintosh HD” is read only..
Do you know how I can resolve this? Is it something to do with system access for sf.ui.file?
Christian Scheuer @chrscheuer2021-02-25 11:19:27.586Z
This is likely caused by macOS having forgotten to ask you for permission to have SoundFlow access that folder. This can happen on Catalina+ due to the hardened runtime.
Go to System Preferences -> Security & Privacy -> Files & Folders and make sure SoundFlow has access:
- AAndrew Sherman @Andrew_Sherman
Thanks Christian, I thought this might be the case so I checked, but I gave Soundflow permissions for Full Disk Access as well as Files and Folders. Does it make a difference whether it's the Soundflow editor or the background app?
Christian Scheuer @chrscheuer2021-02-25 15:39:50.881Z
Does it make a difference whether it's the Soundflow editor or the background app?
We call everything SoundFlow, so ideally you'll never have to make this distinction yourself. Did you do anything manual since you asked this?
- AAndrew Sherman @Andrew_Sherman
It wasn't working with Soundflow having Files and Folders permissions, so I added Full Disk Access to see if it would work. Still not working.
- In reply toAndrew_Sherman⬆:
Christian Scheuer @chrscheuer2021-02-25 15:40:51.323Z
I'd say, it' also pretty likely that you may be trying to save at the root of the drive, which would always give you an error. Make sure to double check the path you're trying to save at :)
- AAndrew Sherman @Andrew_Sherman
Hi Christian, no that's not it; I've been testing it in different locations as well es external drives, and it gives the same error for each location.
Error log:
IOException in file.copy: You can’t save the file “FreshBurrito-02.prproj” because the volume “MP-005” is read only. (Increment 2: Line 19)Christian Scheuer @chrscheuer2021-02-26 15:03:36.987Z
That's odd. The file.copy action generally works (it's used by a lot of customers every day) so there is something specific either in your script or in your volume setup that causes this.
Try to boil it down to a single line of code with string values while debugging, to verify that indeed the paths are correct:
sf.file.copy({ sourcePath: '/Users/chr/Desktop/sourcefile.txt', destinationPath: '/Volumes/MP-005/blabla/blabla', });
Once you have your paths as static strings - provided that you're still seeing issues, once we have that, we can have you try a couple of Terminal commands to use instead while testing/isolating the issue.
Please let us know how the code looks with static paths when you're still seeing the issue.
- AAndrew Sherman @Andrew_Sherman
Hi Christian, I've tried this but I can't get copy working:
sf.file.copy({ sourcePath: '/Users/andrewsherman/VideoTest-01.mov', destinationPath: '/Users/andrewsherman/Movies', });
OException in file.copy: “VideoTest-01.mov” couldn’t be copied to “andrewsherman” because an item with the same name already exists.
Note: there is no file in the "Movies" folder
Raphael Sepulveda @raphaelsepulveda2021-03-02 16:43:46.239Z
What's missing here is the inclusion of the file name in the
destinationPath
, like so:sf.file.copy({ sourcePath: '/Users/andrewsherman/VideoTest-01.mov', destinationPath: '/Users/andrewsherman/Movies/VideoTest-01.mov', });
- AAndrew Sherman @Andrew_Sherman
Aha, thank you. I'll use that and try to debug the bigger script.
- AAndrew Sherman @Andrew_Sherman
So I finally got round to debugging this one and I've got it working now, thanks to the amazing Soundflow community help! For people who are struggling with a script, if you use a "log" at various points in your script you can see what Soundflow is computing for that variable, and in that way I went through it from top to bottom and found a few places where my paths were missing elements.
I do have one more question: how to I get it to add a leading zero to my filename version number? For example FileName-03.ptx
// Get paths and names of selected file in Finder var fullPath = decodeURIComponent(sf.appleScript.finder.selection.getItem(0).asItem.path); var directory = fullPath.split('/').slice(0, -1).join('/'); var filename = fullPath.split('/').slice(-1)[0]; var filenameWithoutExtension = filename.split('.').slice(0, -1).join('.'); var extension = filename.split('.').slice(-1)[0]; // Create a new name (get the name and suffixed number - if your session name was 'My Session 42' then save 'My Session ' in grps[1] and '42' in grps[2]) var grps = filenameWithoutExtension.match(/^(.+?)(\d+)$/); // Construct a new name based on the prefix and the number (+1), followed by the extension suffix. If there was no number, just add ' 2' // This next line will add a suffix if no number is found at end of file (in this case " - V1") var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? '-V01' : ((Number(grps[2]) + 1) + '').padStart(2, '0'); var newFile = (grps ? grps[1] : (filenameWithoutExtension)) + newNumber + '.' + extension; // Save copy of session with new name sf.file.copy({ sourcePath: fullPath, destinationPath: directory + '/' + newFile, }); // Create folder called "Old" if it doesn't exist sf.file.directoryCreate({ path: directory + '/Old', }); // Move original source file to folder called "Old" sf.file.move({ sourcePath: fullPath, destinationPath: directory + '/Old/' + filename, }); // Open new file with default application sf.file.open({ path: directory + '/' + newFile, });
Christian Scheuer @chrscheuer2021-04-12 10:03:55.592Z
Awesome!
Change the newNumber declaration line to:
var newNumber = !grps || grps.length === 0 || isNaN(Number(grps[2])) ? '-V01' : ((Number(grps[2]) + 1) + '').padStart(2, '0');
- AAndrew Sherman @Andrew_Sherman
Excellent, thanks Christian
- AIn reply toAndrew_Sherman⬆:Andrew Sherman @Andrew_Sherman
Wow, thanks Chris. I'm going to have to get my head around what's happening in this one, I can see the sf.ui.file actions will be useful.
- DDaniel Braunstein @Daniel_Braunstein
Hey guys, quick question on this - like I said before, amazing little script. Really helps my workflow! I had an idea and was wondering if there was a way to modify the code to incorporate when there is a modifier at the end of a session file. For Instance, Song1_V2_Clean, to make that change to Song1_V3_Clean, rather than Song1_V2_Clean-V1. This would be really helpful for when I stick a modifier name at the end of a session file and want to keep it at the end and not in the middle.
Thanks guys!
DB- AAndrew Sherman @Andrew_Sherman
Hi Daniel, I'm not sure if you use ChatGPT or something similar, but I've found it quite helpful for Javascript code like this. It requires some trial and error, but I've found the best results taking a script one step at a time and building its functionality bit by bit. For someone like me who doesn't know a whole lot of Javascript that can be quite helpful. I have not tested this, but this is what I got from GPT regarding your question - you may need to modify it or add it to the script in the relevant place.
let str = 'Song1_V2_Clean'; let parts = str.split('_'); // split the string into parts // extract version number (without the 'V') let versionNumber = parseInt(parts[1].substring(1)); // increment version number versionNumber++; // construct new version string parts[1] = 'V' + versionNumber; let newStr = parts.join('_'); // join the parts back together console.log(newStr); // output: Song1_V3_Clean
- DIn reply toAndrew_Sherman⬆:Daniel Braunstein @Daniel_Braunstein
Guys - thank you for this treasure. What an amazing script. I've been digging for hours trying to find a way to do it with Mac automator to no avail... This just saved me a lot of clicks and a lot of forgetting to save a new version!!!