renaming finder items IV — part 3

the last two posts looked at using applescript to develop an app to make batch renaming finder items even easier than automator, or bridge, or whatever. first, we created the basic functionality, then we improved the script to make it more user friendly. this post will be the final instalment — the final revision to optimise usability.

to understand this post you’ll first need to review renaming finder items IV and renaming finder items IV — part 2.

the app has been created as a droplet — drop files on the app’s icon and they are processed by the script. pretty simple. but the app would be better still if it could be activated in a number of ways. so, we’re going to alter it so that, as well as accepting dropped files, it can accept a dropped folder of files, or just run when it is double-clicked.

it would also be better if it ran faster.

making it faster is easy. in its current form the script will rename 1000 files in a bit over a minute — not bad. but if you take every instance of this line :

tell application "Finder"

and replace it with :

tell application "System Events"

you’ll find that renaming 1000 files will take about a second. that’s even more not bad.

“System Events is an agent (or faceless background application) that supplies the terminology for using a number of features in AppleScript scripts. … Provides terminology to access disks, files, and folders without targeting the Finder. This can be more efficient than using the Finder …”
Mac Developer Library

ok, so much for speed, now for the varied functionality. the original script started with an on open command and assumed that the user would be dropping a bunch of files on the droplet.

here’s how to change that on open command so that it could accept either a bunch of files or a folder :

on open (mgDropped)
  tell application "System Events"
    if (count of mgDropped) = 1 then
      if (mgDropped as string) ends with ":" then
        set mgFiles to files of item 1 of mgDropped
      else
        display dialog "You're really using a script to rename one file?" & return & return & "OK ... whatever."
        set mgFiles to mgDropped
      end if
    else
      set mgFiles to mgDropped
    end if
  end tell
  my mgProcess(mgFiles)
end open

the files to be renamed are now defined by the variable mgFiles before being passed to the subhandler mgProcess. the reason we are now using a subhandler for the renaming is that the on open command is not the only script initiator — we’ll also be including an on run command.

the on run command is what allows the app to run by just double-clicking it. when the app is double-clicked it will ask the user to choose a folder of files to rename — again passing those files to the mgProcess subhandler. so the full start of the script now looks like this :

-- thanks to Yvan Koenig for the on run/on open clue :
-- http://macscripter.net/viewtopic.php?id=41214
on run
  set mgFolder to choose folder
  tell application "System Events"
    set mgFiles to files of mgFolder
  end tell
  my mgProcess(mgFiles)
end run

on open (mgDropped)
  tell application "System Events"
    if (count of mgDropped) = 1 then
      if (mgDropped as string) ends with ":" then
        set mgFiles to files of item 1 of mgDropped
      else
        display dialog "You're really using a script to rename one file?" & return & return & "OK ... whatever."
        set mgFiles to mgDropped
      end if
    else
      set mgFiles to mgDropped
    end if
  end tell
  my mgProcess(mgFiles)
end open
---------------------------------------------

so, to reiterate, having both the on run and on open commands allows this script to behave both as a standard app and as a droplet.

there are a bunch of other minor revisions that need to be made to the original script to accommodate this new functionality — not least the encapsulating of the basic script within a subhandler. the new completed script would look like this :
screen grab of finished EasyRenaming script

but you can get the complete EasyRenaming app here.

have fun improving this script for even greater functionality.


• related post : renaming finder items : renaming using automator or applescript.
• related post : renaming finder items II : renaming by list.
• related post : renaming finder items III : renaming by creation date.

macgrunt icon

renaming finder items IV — part 2

today we’re going to look at revising the script from the last post to make it more user friendly and robust. here was what we finished with last time :
screen grab of finished script

you may need to review how that script functions before proceeding : renaming finder items IV

refining a script for optimal usability is largely a matter of asking a bunch of “what if …?” questions. what if the user does this? what if the user does that? what if the user is a thicky? etc.

we’re going to do some simple refinements :

  • what if the user enters nothing in the first dialog? — we’ll give them the option to append something to the start of the filename.
  • what if the user enters nothing in the second dialog? — we’ll give them the option to simply delete the ‘change from’ text from the filename.
  • what if the user doesn’t fully understand what the script is going to do? — we’ll give them a confirmation message before proceeding.
  • how will we know how successful the script was? — we’ll include some extra reporting at the end of the process.

first we’ll add three variables right after the first line of the script :

set mgAppend to "NUP"
set mgChanged to {}
set mgUnChanged to {}

the first is a simple switch we’ll use for an if/then statement when it comes time to actually change the filenames. the other two are empty lists that will get populated as the script processes the files. these will be the basis for the end reporting.

the next part goes straight after presenting the change from dialog. it creates another window to check what to do if the user enters nothing in the change from dialog. this is where the mgAppend variable gets switched if required :

set mgChangeFrom to text returned of mgChangeFromDialog
if mgChangeFrom is "" then
  set mgJustChecking to display dialog "So, do you want to append the new text to the front of the filename?" buttons {"No — Cancel", "Yes — Append"} default button 2
  if button returned of mgJustChecking is "No — Cancel" then
    error number -128
  else
    set mgAppend to "YEP"
  end if
end if

screen grab of append check dialog window

does that all make sense so far?

now after collecting the change from and change to data from the user and running the considering case check, we’ll have three possible scenarios (apart from the script having been cancelled) :

  1. we have nothing for change from and a text string for change to (append)
  2. we have a text string for both change from and change to (replace)
  3. we have a text string for change from but nothing for change to (delete)

we can use those possible scenarios to build one of three possible confirmation dialogs like this :

if mgChangeTO is not "" then
  if mgAppend is "YEP" then
    set mgConfirmationText to "JUST CONFIRMING." & return & return & "Filenames will be changed so that :" & return & "      '" & mgChangeTO & "'." & return & "is appended to the front of each filename." & return & return & "IS THAT CORRECT?"
  else
    set mgConfirmationText to "JUST CONFIRMING." & return & return & "Filenames will be changed so that :" & return & "      '" & mgChangeFrom & "'" & return & "becomes" & return & "      '" & mgChangeTO & "'." & return & return & "IS THAT CORRECT?"
  end if
else
  set mgConfirmationText to "JUST CONFIRMING." & return & return & "Filenames will be changed so that :" & return & "      '" & mgChangeFrom & "'" & return & "is deleted." & return & return & "IS THAT CORRECT?" & return & return & return & "{WARNING : this change cannot be undone}"
end if

set mgConfirmation to display alert mgConfirmationText buttons {"HELL NO! STOP", "YES! Go do that thing"} default button "HELL NO! STOP" as critical
if button returned of mgConfirmation is "HELL NO! STOP" then
  error number -128
end if

screen grab of three confirmation dialog windows

the portion of the script that does the actual name changing is a bit more complicated now. it needs to allow for appending (as required) and for populating those two lists we need for reporting purposes :

tell application "Finder"
  repeat with mgitem in mgSelection
    set mgOriginalName to name of mgitem
    if mgAppend is "YEP" then
      set mgNewName to (mgChangeTO & mgOriginalName) as string
      set name of mgitem to mgNewName
      set end of mgChanged to mgOriginalName
    else
      set text item delimiters of AppleScript to mgChangeFrom
      set mgNameItems to text items of mgOriginalName
      set text item delimiters of AppleScript to ""
      set mgItemCount to count mgNameItems
      if mgItemCount is not 1 then
        set text item delimiters of AppleScript to mgChangeTO
        set mgNewName to mgNameItems as string
        set text item delimiters of AppleScript to "."
        set mgNameCheck to text item 1 of mgNewName
        set text item delimiters of AppleScript to ""
        if mgNameCheck is "" then
          display dialog "Sorry, You cannot have a file with no name at all" buttons "dammit" default button 1
          error number -128
        end if
        set name of mgitem to mgNewName
        set end of mgChanged to mgOriginalName
      else
        set end of mgUnChanged to mgOriginalName
      end if
    end if
  end repeat
end tell

and, finally, we have the reporting process. the original script simply popped up a window saying “All Done”. this version will give the user some helpful feedback :

set mgCountSelection to count mgSelection
set mgCountChanged to count mgChanged
set mgCountUnChanged to count mgUnChanged

if mgUnChanged is not {} then
  if mgCountUnChanged is less than 12 then
    set mgUnChangedList to "They are: " & return & "    "
    repeat with mgUnChangedItem in items of mgUnChanged
      set mgUnChangedList to mgUnChangedList & mgUnChangedItem & return & "    "
    end repeat
  else
    set mgUnChangedList to "Here are some of them: " & return & "    "
    repeat with x from 1 to 10
      set mgUnChangedItem to item x of mgUnChanged
      set mgUnChangedList to mgUnChangedList & mgUnChangedItem & return & "    "
    end repeat
  end if
end if

if mgCountSelection = mgCountChanged then
  set mgMessage to "All " & mgCountSelection & " files were successfully renamed."
else if mgCountSelection = mgCountUnChanged then
  set mgMessage to "Sorry, none of the " & mgCountSelection & " files were successfully renamed." & return & return & "You probably stuffed something up."
else
  set mgMessage to (mgCountChanged & " filenames were successfully renamed." & return & return & (count mgUnChanged) & " filenames were NOT changed because " & return & "they do not contain '" & mgChangeFrom & "'." & return & return & mgUnChangedList) as string
end if

display dialog mgMessage

the dialog will list the names of the unchanged files. if there are too many it will just list the first 10 :
screen grab of the final reporting dialog window

the finished script would look something like this.
screen grab of the finished script

there’s a bunch of different ways to improve this script. the next post will look at changing it so that it can be activated in a number of ways (rather than only by dropping files) and how to make the finished app run MUCH faster.

til then, keep grunting.


• related post : renaming finder items : renaming using automator or applescript.
• related post : renaming finder items II : renaming by list.
• related post : renaming finder items III : renaming by creation date.

macgrunt icon

renaming finder items IV

when we first started looking at batch renaming files, we saw how you could use automator to replace part of a filename with new text (see renaming finder items). here’s an applescript version of that functionality which makes batch renaming files in the finder even easier than automator.

this script works on batches of filenames that have something in common (eg. ‘12345 ThisFile_01’, ‘12345 ThisFile_02’, etc.) allowing the user to replace or add to that common element (eg. ‘ABCD_ThisFile_01’, ‘ABCD_ThisFile_02’, etc. … or … ‘12345 ThisFinishedFile_01’, ‘12345 ThisFinishedFile_02’, etc.)

the first part of the script references the selected finder items (the files to be changed) and the name of the first of these files. we use text item delimiters to strip the extension from that name :

tell application "Finder"
  set mgSelection to the selection
  set mgFirstName to name of item 1 of mgSelection
  set text item delimiters of AppleScript to "."
  set mgFirstName to text item 1 of mgFirstName
  set text item delimiters of AppleScript to ""
end tell

the next bit presents the user with two dialog windows. the first captures the change from text — with the filename from above as the initial default answer. the second captures the change to text — with the change from text as the initial default answer. including default answers in the dialog windows is a handy prompt for the user :

set mgChangeFromDialog to display dialog "What part of the filename do you want changed?" default answer mgFirstName with title "THIS SCRIPT CHANGES FILENAMES"
set mgChangeFrom to text returned of mgChangeFromDialog
delay 1
set mgChangeToDialog to display dialog "What is your new replacement text?" default answer mgChangeFrom with title "THIS SCRIPT CHANGES FILENAMES"
set mgChangeTO to text returned of mgChangeToDialog

here’s an example to make things clearer (hopefully). the first dialog is the change from window showing the first filename as it is presented to the user (left). on the right is that same window after the user specifies the part of the filename to change, before hitting ok :
screen grab of the change from dialog window

then the next dialog window — change to — pops up with that same user-defined text as the prompt (left). and the user specifies what that text should be changed to before hitting ok again (right) :
screen grab of the change to dialog window

you’ll notice in that script snippet above a one second delay between the presentation of the windows. this is purely a psychological gap to help the user realise that a new window has been presented. try it without the delay if you prefer.

next, just for shits and giggles, here’s a surly little message just in case the change from text is the same as the change to text. of course, this is entirely unnecessary — but good for a laugh :

considering case
  if mgChangeFrom = mgChangeTO then
    display dialog "OK — so you don't want any change at all." & return & return & "Thanks for wasting my time tosser." buttons {"get stuffed"} default button 1 giving up after 2
    error number -128
  end if
end considering

screen grab of surly message

that snippet demonstrates the handy ‘considering case’ control statement. this means the message would only get triggered if you tried to change “ThisFile” to “ThisFile” but not for “THISFILE” or “thisFILE” or “Thisfile” etc.

the rest of the script looks like this :

tell application "Finder"
  repeat with mgitem in mgSelection
    set mgOriginalName to name of mgitem
    set text item delimiters of AppleScript to mgChangeFrom
    set mgNameItems to text items of mgOriginalName
    set text item delimiters of AppleScript to ""
    set mgItemCount to count mgNameItems
    if mgItemCount is not 1 then
      set text item delimiters of AppleScript to mgChangeTO
      set mgNewName to mgNameItems as string
      set text item delimiters of AppleScript to ""
      set name of mgitem to mgNewName
    end if
  end repeat
end tell
set mgMessage to "All Done."
display dialog mgMessage

let’s use the examples from the dialog windows above to explain this part of the script. the files we are changing are pdfs. mgChangeFrom is ‘ThisFile’ and mgChangeTo is ‘ThisFinishedFile’. On the first pass through the loop, mgOriginalName will be the name of the first item in the Finder selection — ‘12345 ThisFile_01.pdf’. We set applescript’s text item delimiters (the characters which separate sections of text) to mgChangeFrom (‘ThisFile’) and then get the text items of mgOriginalName. this will give us the variable mgNameItems as a list — {“12345 “, “_01.pdf”}.

next we do a count of mgNameItems. if the list has only one item, it means mgOriginalName DOES NOT contain mgChangeFrom (eg. the filename ‘54321 ThatFile_31.pdf’ would return the list {“54321 ThatFile_31.pdf”}) and that file will be skipped over because the rest of the functionality is held within that if statement.

incidentally, if mgChangeFrom in our working example was ‘12345’ instead of ‘ThisFile’, the list returned would be {“”, ” ThisFile_01.pdf”} — that is, two list items — so it would pass the requirements of the if statement.

so, if the filename passes that if requirement we then change the text item delimiters to mgChangeTo (‘ThisFinishedFile’) and run the list items back together as a string — that is, {“12345 “, “_01.pdf”} becomes “12345 ThisFinishedFile_01.pdf”.

and finally there’s the line which actually does the name change. then the process is repeated for all the items in mgSelection. and we end with a message which alerts the user to the fact that the process is complete.

to turn this into a functioning app you’d add an on open handler and delete (or comment out) the line “set mgSelection to the selection”. the finished script would look like this :
screen grab of finished script

then you’d save it as an application, drag it to your sidebar, and drag-drop the files you want to rename onto it (left). and quicker than you can say “jiggle my janglers” the work’s done (right). you can see here how files that do not contain the change from text are simply skipped over :
screen grab of files being dropped onto app and finished result

so, this is the basic functioning script. but the next post will look at how to make it just a little more user friendly and robust. we need to add some error handling for different scenarios and maybe some other user safeguards.

’til then, keep grunting.


• related post : renaming finder items : renaming using automator or applescript.
• related post : renaming finder items II : renaming by list.
• related post : renaming finder items III : renaming by creation date.
• related post : renaming finder items IV — part 2 : the next step.

macgrunt icon

renaming finder items III

renaming finder items introduced a couple of ways to batch-rename files in the finder — using automator or applescript — by replacing characters or removing parts of the file name. renaming finder items II showed how to map a bunch of files to a completely different set of file names.

Applescript Icon this post will show another way to rename files — based on their creation date — using applescript. this workflow came about due to the diabolical way that digital cameras name their files. here’s a screen grab of a typical bunch of images :
screen grab of folder of images with diabolical file names

for this workflow, we’d like the order of the filenames to match the date the photo was taken — to make sorting, selecting, archiving, etc. just a little easier. the current naming is made more problematic because files are coming from more than one camera. well, we don’t have to put up with that crap — let’s just rename them.

set mgFolder to choose folder
tell application "Finder"
  set mgFiles to items of mgFolder
end tell
repeat with mgFile in mgFiles
  set mgPath to POSIX path of (mgFile as string)
  set mgCreation to creation date of mgFile
  set mgDate to short date string of mgCreation
  set mgTime to time string of mgCreation
  
  -- rest of script here
end repeat

first we get the user to select a folder of images to process, then we get references to the files in that folder, then we start a repeat loop to process each file in turn. we capture the following information :
mgCreation – for example “Thursday, 4 October 2012 17:13:18 ” – from which we extract :
mgDate – “04/10/12” (that’s dd/mm/yy) and
mgTime – “17:13:18 ” (that’s hh/mm/ss)

if you find that mgDate or mgTime give you the data in a different format, you will need to update the next part of the script accordingly or, if you prefer, change your language and text system preferences :
screen grab showing language and text system preferences window

we’re going to use mgDate and mgTime to create a file name like this “yymmdd_hhmmss” — so, we need to strip out the slashes and colons, reverse the order of the date, and introduce an underscore :

  set text item delimiters of AppleScript to ":"
  set mgTime to text items of mgTime --gives {"17", "13", "18 "}
  set text item delimiters of AppleScript to "/"
  set mgDate to reverse of text items of mgDate --gives {"12", "10", "04"}
  set text item delimiters of AppleScript to ""
  set mgDate to mgDate as string --gives "121004"
  set mgTime to mgTime as string --gives "171318 "
  set mgTime to text items 1 thru 6 of mgTime as string
  
  set mgNewName to mgDate & "_" & mgTime

this uses the handy “reverse of” command — taking a list of items and rearranging them in reverse order. also notice the second last line — we need that because mgTime has a trailing space we need to get rid of. this method works no matter how many trailing spaces there are because we’re saying “just give me the first six characters and ditch the rest”.

now, that’s the bulk of the script done — we also need to capture the extension of the file (mgExtension), create a filepath for the renamed file (mgFinalPath), and then do the renaming with a shell script. the shell script basically says — move this file, without any prompts, from this filepath to that filepath (you could also use this method to move the file to a completely different location — but we’re just renaming the file and leaving it in the same location) :
danger : do not run this next script — it is for demonstration purposes only – you’ve been warned!

set mgFolder to choose folder
tell application "Finder"
  set mgFiles to items of mgFolder
end tell

repeat with mgFile in mgFiles
  set mgPath to POSIX path of (mgFile as string)
  set mgCreation to creation date of mgFile
  set mgDate to short date string of mgCreation
  set mgTime to time string of mgCreation
  
  set text item delimiters of AppleScript to ":"
  set mgTime to text items of mgTime
  set text item delimiters of AppleScript to "/"
  set mgDate to reverse of text items of mgDate
  set text item delimiters of AppleScript to "."
  set mgExtension to text item -1 of (mgFile as string)
  set text item delimiters of AppleScript to ""
  set mgDate to mgDate as string
  set mgTime to mgTime as string
  set mgTime to text items 1 thru 6 of mgTime as string
  
  set mgNewName to mgDate & "_" & mgTime

  set mgFinalPath to mgFolder & mgNewName & "." & mgExtension as string

  do shell script "mv -f " & quoted form of POSIX path of mgPath & space & quoted form of POSIX path of mgFinalPath
end repeat

if we run this script on that original folder of images we’ll get this :
screen grab of file names after processing with first script

well, that looks pretty good at first glance — all the files have been renamed successfully … except … the very observant will notice that this folder has only 81 items, whereas the original folder had 103. wtf?

the problem is that it’s quite possible to have more than one file with a creation date of “04/10/12 17:13:18” — digital cameras can flick off quite a few images in a second. the script in its current form will simply write over a file with a duplicate filename. so we need to build in some method to check if a file with a particular name already exists and, if so, create a different name. here’s one way :

  set mgFinalPath to mgFolder & mgNewName & "_1" & "." & mgExtension as string
  tell application "Finder"
    set x to 2
    repeat
      set mgExists to exists file mgFinalPath
      if mgExists is true then
        set mgFinalPath to mgFolder & mgNewName & "_" & x & "." & mgExtension as string
        set x to x + 1
      else
        exit repeat
      end if
    end repeat
  end tell

this time we’re appending “_1” to each filename. then the script enters a repeat loop to check if a file with that name already exists. if so, it will change the end to “_2” and then check that, incrementing by one on each pass through the loop. once it reaches a name that has not already been used it exits the repeat loop. this way we don’t lose any files :
screen grab of filenames using final script

this is the final form of the script. it’s always a good idea to test new scripts on duplicate files before implementing them into your workflow — just to make sure they do what you expect (and don’t do what you don’t expect) — you’ve been warned again :

set mgFolder to choose folder
tell application "Finder"
  set mgFiles to items of mgFolder
end tell

repeat with mgFile in mgFiles
  set mgPath to POSIX path of (mgFile as string)
  set mgCreation to creation date of mgFile
  set mgDate to short date string of mgCreation
  set mgTime to time string of mgCreation
  
  set text item delimiters of AppleScript to ":"
  set mgTime to text items of mgTime
  set text item delimiters of AppleScript to "/"
  set mgDate to reverse of text items of mgDate
  set text item delimiters of AppleScript to "."
  set mgExtension to text item -1 of (mgFile as string)
  set text item delimiters of AppleScript to ""
  set mgDate to mgDate as string
  set mgTime to mgTime as string
  set mgTime to text items 1 thru 6 of mgTime as string
  set mgNewName to mgDate & "_" & mgTime
  
  set mgFinalPath to mgFolder & mgNewName & "_1" & "." & mgExtension as string
  tell application "Finder"
    set x to 2
    repeat
      set mgExists to exists file mgFinalPath
      if mgExists is true then
        set mgFinalPath to mgFolder & mgNewName & "_" & x & "." & mgExtension as string
        set x to x + 1
      else
        exit repeat
      end if
    end repeat
  end tell
  
  do shell script "mv -f " & quoted form of POSIX path of mgPath & space & quoted form of POSIX path of mgFinalPath
end repeat

now, of course, you can run this script for any kind of file, not just images. but in its current form it has no error handling for if it encounters a folder. we’ve fixed it so it won’t have a hassle with duplicate files, but if it hits a folder you’ll get something like this :
screen grab of error message if script encounters a folder

dammit you say. but the fix is actually very simple — just change the line “set mgFiles to items of mgFolder” to “set mgFiles to files of mgFolder” and the folders will be skipped — no drama.

but that’s not all …
just to be safe, or because your workflow calls for it — you could change the script so that it makes a copy of the file — giving the new name to the copy and leaving the original untouched.

and again, the solution is very simple — change the shell script from move (mv) to copy (cp) :

do shell script "cp -f " & quoted form of POSIX path of mgPath & space & quoted form of POSIX path of mgFinalPath

you can get a copy of the RenameByDate app here

keep grunting


• related post : renaming finder items : renaming using automator or applescript.
• related post : renaming finder items II : renaming by list.
• related post : renaming finder items IV : easy renaming.

macgrunt icon

turn an applescript into a service

the applescript outlined in email file from finder II takes selected items in the finder and attaches them to a new outgoing email message. it allows for multiple recipients from your address book and automatically adds a subject line and some basic content to the email.

there are a few ways to make that applescript accessible in the finder but the most convenient is to turn it into a service. this method is for OS X 10.6 and later — you can see a similar procedure for earlier operating systems in the post get file path of finder items.

Automator Iconapplescript services can be created through automator — the automation program that comes standard on every mac since OS X 10.4 (you’ll find it in your applications folder).

when you first open automator you’ll see this screen — choose service :
screen grab of automator startup screen

this next screen grab shows a couple of different things you need to do. in the main window on the right are two dropdowns — set the first to files or folders and the second to finder. in the search field on the left type ‘applescript’ to easily find the run applescript action. drag and drop this action into the right window :
screen grab of automator with service being built

then paste your applescript into that action window — you can entirely replace the default script. this is what it should look like after you hit the compile button (hammer) :
screen grab of automator service window with applescript in place

save that out and you’re done. now, whenever you want to email something from the finder just select it and right-click — you’ll see your service near the bottom of the contextual menu :
screen grab showing the service as a contextual menu item

you can also activate it under finder > services :
screen grab of services menu

under that menu you can also access the services preferences. this allows you to even set a keyboard shortcut for your new handy service :
screen grab of services preferences window

of course, services aren’t just restricted to running your applescripts. investigate the full power of automator … and get grunting.

macgrunt icon