Using Applescript & CSV to partial rename files

Ls! Here's my problem; I need an applescript to rename a massive amount of jpgs by a CSV-list. But only part of the name has to change, not the whole name.


The naming logic in this folder is aaaaa_1.jpg, aaaaa_2.jpg, aaaaa_3.jpg, bbbbb_1.jpg, bbbbb_2.jpg etc) I want only the first 5 digits to be replaced, while keeping the '_x.jpg' part. There's a CSV with two colums: oldname (aaaaa, bbbbb, etc) and newname (aa-X-X-X, bb-X-X-X etc)


So far I've only found ways to deal with this by changing the CSV. But how would I make the script with the current CSV. Thanks!!!

Posted on Nov 20, 2016 6:17 AM

Reply
13 replies

Nov 21, 2016 1:45 AM in response to VikingOSX

the csv looks like this:

14001 14001-name-description-mim

14002 14002-name-description-mim

14003 14003-name-description-mim

(so no first line with colum-names and delimiter is 'tab' but can be ';')

In my formulating the problem, X stands for a longer text


I have no knowledge of C or Python but I can work with applescript, automator and hey, even do some php. But I'm on a journey to learn.

Nov 21, 2016 9:07 AM in response to MeatWork@Adam

Based on the CSV file and Image Folder as inputs, this AppleScript application will use the new namestring field in the CSV as the filename (partial) replacement string, and then rename the image files in the same folder. Recommend that you make a test CSV that matches a test Image folder, and test the script on that first.


You don't need to learn AppleScript, Objective-C, or Python to use this script.


The AppleScript code uses two handlers (functions):

  • getfiles - AppleScript/Objective-C

    Presents custom File Chooser where you select (in order) your CSV file and image folder. Order is critical.

  • rename_files - Python

    Makes a list of image files, reads the CSV, replaces name strings, and renames image files in same folder


Code Assumptions:

  • No header row
  • No quoted fields
  • No deliberate Unicode CSV processing
  • CSV delimiters will be automatically detected if they are:
    • Comma
    • Semi-Colon
    • Colon
    • Bar '|'
    • Tab
  • No sub-folder processing in image folder

Note: Other than CSV delimiters, the other bullets are possible, but costly in complexity, and code/test time. One can consult the Python csv module for more detail.


Installation/Usage:

  1. Open your Script Editor (Launchpad : Other : Script Editor).
  2. Copy/Paste the following code into your Script Editor
    1. Click the Compile (hammer icon) button (multi-color text without errors indicate ready to run state)
    2. Press control+command+R keys to run this script. (It will hang if you just click the Run button).
    3. Select your CSV file first, press the command-key, and then select the Image folder.
    4. A "Done" dialog indicates that your filenames have been renamed.
  3. To make this script a clickable application on your Desktop
    1. File menu : Save... (save a copy of the script source first)
      1. Save Format: text
      2. Name: img_rename.applescript
    2. Option-key + File menu : Save As... (now make an application)
      1. Save Format: application
      2. Name img_rename
      3. Hide Extension is selected
      4. Put it on your Desktop
      5. Double-click to run


-- img_rename.applescript
--
-- Select CSV file, and folder containing files to rename (in that order)
-- Will automatically detect comma, semi-colon, and tab delimiters in CSV
-- Replacement strings in CSV will be applied, and folder's files renamed.
-- Assumptions: • CSV headers unsupported
-- • 2 columns of unquoted CSV content
-- -
--
-- Inputs: 1) CSV file, 2) Folder containing files to rename
-- Tested: OS X 10.11.6, macOS 10.12.1 with System Python 2.7.10
-- Version 1.0
-- VikingOSX, Nov. 20, 2016, Apple Support Communities
property ca : current application

property default_loc : "~/Desktop"
property file_types : {"public.comma-separated-values-text", "public.plain-text"}
use framework "AppKit"
use AppleScriptversion "2.4" -- Yosemite or later
use scripting additions

on run
set alert_msg to "This script must be run from the main thread." & return & "Either press control+command+R to run it, or save it as a script or application."
-- check we are running in foreground
if not (ca'sNSThread'sisMainThread()) as boolean then

display alertalert_msgbuttons {"Cancel"} ascritical

error number -128
end if

set openstuff to getfiles("POSIX") as list

tell application "Finder"

set fileCnt to number of files in folder ((last item of openstuff) as POSIX file)
end tell

if fileCnt = 0 then
display dialog "Empty Folder specified."
return

end if

set args to (first item of openstuff) & space & (last item of openstuff)'s quoted form

set status to rename_files(args) as text

display dialogstatus

return
end run

on getfiles(afmt)
-- custom choose file(s) and folder(s). Can return POSIX, or HFS format styles
set Msg to "Select CSV file, and then with command-key, select Folder."
set returnCode to 0 as integer

if default_loc starts with "~" or adir starts with "../" then

-- relative POSIX paths to standard paths conversion
set tpath to ca'sNSString'sstringWithString:default_loc
set stdpath to tpath'sstringByStandardizingPath() as text
end if
-- custom file chooser
set oPanel to ca'sNSOpenPanel'sopenPanel()

tell oPanel

its setFloatingPanel:true -- over the top of all other windows

its setDirectoryURL: (POSIX filestdpath)
its setFrame: (ca'sNSMakeRect(0, 0, 720, 525)) display:yes
its setTitle:"Open"
its setMessage:Msg
its setPrompt:"Select"
its setAllowsMultipleSelection:true
its setAllowedFileTypes:file_types
its setCanChooseFiles:true
its setCanChooseDirectories:true
its setTreatsFilePackagesAsDirectories:false
end tell
set returnCode to oPanel'srunModal()

if returnCode is (ca'sNSFileHandlingPanelCancelButton) then

error number -128

end if
-- set the POSIXpaths to (oPanel's |URL|()'s valueForKey:"path") as list
if afmt is "HFS" then
return (oPanel's URLs) as list

else if afmt is "POSIX" then

return (oPanel's filenames()) as list

else

-- neither is not an option, force user cancel

error number -128
end if

return

end getfiles

on rename_files(vargs)

-- Read CSV file containing current and replacement filename strings.
-- Rename files in specified folder (not sub-folders) to replacement syntax.
return do shell script "python <<'EOF' - " & vargs & "
#!/usr/bin/python
# coding: utf-8
import csv
import os
import sys
csvdelim = ',;:|\\t'
if not len(sys.argv) == 3:
print 'Invalid argument list'
sys.exit(1)

csvfile, folder = map(str, [a.decode('utf-8') for a in sys.argv[1:]])
if not (os.path.isfile(csvfile) and os.path.isdir(folder)):
print 'Either CSV or Folder is missing'
sys.exit(1)
names_file = os.path.expanduser(csvfile)
f = os.path.expanduser(folder)
#if not os.listdir(f):
# print 'Folder is empty'
# sys.exit(1)
# no .DS_Store files
flist = sorted(filter(lambda f: not f.startswith('.'), os.listdir(f)))
t = ()
# read the CSV and make a tuple of tuples from the rows/columns
# e.g. (('old', 'new'), ('old', 'new'), ...)
with open(names_file, 'rb') as xcsv:
# automatically detect CSV delimiter if in csvdelim variable

try:

dialect = csv.Sniffer().sniff(xcsv.read(), delimiters=csvdelim)
except csv.Error as e:
print('{} - {}'.format(str(type(e)), str(e.args)))

sys.exit(1)
xcsv.seek(0)

thedata = csv.reader(xcsv, dialect)
for row in thedata:
t += ((row[0], row[1],),)

# make new list of files with replacement names

newfiles = [reduce(lambda s, kv: s.replace(*kv), t, x) for x in flist]

# change directory to target directory and do renames

os.chdir(f)

for i in range(0, len(flist)):

os.rename(flist[i], newfiles[i])

print('Done')

EOF"

end rename_files


I copy/pasted the above script back into Script Editor from this page, and it ran correctly.

Nov 24, 2016 1:17 AM in response to VikingOSX

Nope. Doesn't work for me, because - as I found out through trial and error - this only works for Yosemite and applescript 2.4 etc. The 'highest' I get with all the different OS's here is 10.9.5 (mavericks and a whole zoo of OS's) - I use my macs for imagery and productivity so updating to the latest isn't on my priority list.


But it raises a new question for me. Is it because of the partial replacement in the filename, that this is so hard to do in just a Applescript?

Nov 24, 2016 4:38 AM in response to MeatWork@Adam

Well, I should have asked you what the target operating system was for this script. My bad. You may still have a solution based on the following choice:


  • AppleScript
    1. Move the Objective-C handler functionality into the Python code
      1. Removes AppleScript version restriction
    2. Apple has tweaked AppleScript over the years and those syntax eccentricities may break current tested code run on older versions of AppleScript.
    3. Keep double-click application on Desktop, but may require to be saved as Application again on older releases of OS X.
  • Exclusive Python application
    1. Already written. Add the Objective-C file/folder GUI (already written).
    1. Eliminate AppleScript entirely.
    2. Runs from Terminal command-line
    3. Python too, has evolved, and I will test the code as written on 10.6.8 to verify I am not using features not found on earlier Python versions.
    4. Develop syntax clean Python code with lint tools


    Python has data structures and routines that are missing from AppleScript. For large directories of filenames that require partial name replacement, I believed that the advanced features in Python would be far more efficient in processing speed, with reduced coding effort. Apart from getting the CSV content into lists of two-pair tuples, there is just one line of code that actually does the name string replacement in a loop over existing files.


    Let me know which approach you prefer, if you want to move forward with the above choices.

    Nov 27, 2016 9:41 AM in response to MeatWork@Adam

    Hello


    You might try the following AppleScript script which is a mere wrapper of bash script. It will let you choose source directory and CSV file (tab separated values) and then rename jpg files in the source directory according to name mappings given in the CSV file.


    Briefly tested under OS X 10.6.8 but no warranties of any kind. Please make sure you have complete backup of the original directory before running this sort of script. You'd better test with small subset first.


    (For testing purpose, you may set DEBUG=1 in the shell script to print the CSV parsing result only, and DEBUG=2 to print the old and new file name mapping only. Currently DEBUG=0, which will actually rename files. Any output from shell is given in the result pane/window of Script Editor)



    --APPLESCRIPT set d to (choose folder with prompt "Choose source folder of jpeg images")'s POSIX path set c to (choose file of type {"csv", "txt"} with prompt "Choose CSV file of name mappings")'s POSIX path set args to "" repeat with a in {d, c} set args to args & a's quoted form & space end repeat do shell script "/bin/bash -s <<'EOF' - " & args & " # $1 : source directory # $2 : CSV file DIR=${1%/} # source directory EXT='(jpg|jpeg)' # name extension (case insensitive) CSV=$2 # CSV file SEP=$'\\t' # CSV field separator (currently tab) # DEBUG mode # 0 : normal run # 1 : only print old and new name pair parsed from CSV # 2 : only print old and new file pair to be used in mv(1) DEBUG=0 exec 2>&1 export LC_CTYPE=UTF-8 shopt -s nocasematch shopt -s nullglob while read -d $'\\0' -r s do # read old and new name prefix pair ($a -> $b) [[ $s =~ ^([^${SEP}]+)${SEP}(.+)$ ]] || continue a=${BASH_REMATCH[1]} b=${BASH_REMATCH[2]} (( DEBUG == 1 )) && { printf '[%s] -> [%s]\\n' \"$a\" \"$b\" continue } # iterate through files starting with old name prefix ($a) in $DIR directory, # and if the name matches the pattern as old name prefix ($a) + (_[0-9]+)? + .(jpg|jpeg) # then replace the old name prefix ($a) with the new name prefix ($b) for f in \"$DIR/$a\"* do # get suffix (_[0-9]+) and name extension (.jpg or .jpeg) tail=${f#$DIR/$a} [[ $tail =~ ^(_[0-9]+)?(\\.$EXT) ]] || continue suff=${BASH_REMATCH[1]} ext=${BASH_REMATCH[2]} # new file name ($f1) f1=\"$DIR/$b$suff$ext\" (( DEBUG == 2 )) && { printf '[%s] -> [%s]\\n' \"$f\" \"$f1\" continue } # rename $f to $f1 (( DEBUG == 0 )) && { mv -v \"$f\" \"$f1\" } done done < <(perl -0777 -l0pe 's/\\015\\012|\\015|\\012/\\0/og' \"$CSV\") # translate CRLF|CR|LF to \\0 exit 0 EOF" --END OF APPLESCRIPT



    Good luck,

    H

    Nov 25, 2016 9:11 PM in response to MeatWork@Adam

    I have rewritten the AppleScript/PyObjC script to do everything within Python, and avoid any AppleScript version issues. This new code has been tested in the Script Editor, and as a standalone application, on OS X 10.6.8, OS X 10.11.6, and macOS 10.12.1. It works correctly on each of these operating systems without modification.


    Code:


    -- img_rename.applescript
    --
    -- AppleScript/PyObjC script that will rapidly make partial filename
    -- renames based on second field in CSV file. Uses Cocoa NSOpenPanel to
    -- allow user to select (in order) CSV file, and image folder.

    -- Tested: OS X 10.6.8, OS X 10.11.6, macOS Sierra 10.12.1
    -- Version: 2.0
    -- VikingOSX, Nov. 25, 2016, Apple Support Communities

    property default_folder : "~/Desktop"

    set status to ""

    try

    status = rename_files(default_folder)

    tell application "System Events"

    display dialog "File processing complete." giving up after 10

    end tell

    on error errmsgnumbererrnbr

    my error_handler(errnbr, errmsg)
    end try

    tell current application to if it is running then quit

    return

    on rename_files(afolder)
    return do shell script "python <<'EOF' - " & afolder's quoted form & "

    #!/usr/bin/python
    # coding: utf-8
    from Foundation import NSURL, YES, NO, NSMakeRect
    from AppKit import NSOpenPanel, NSFileHandlingPanelOKButton

    import csv
    import os
    import sys


    csvdelim = ',:;\\t'
    ret = ()
    def get_files(the_folder):
    # get the CSV file, and the image folder in one visual interface
    msg = 'Select CSV file, then with Command key, select image folder.'
    title = 'Get CSV and Image Folder'
    file_types = ['public.comma-separated-values-text', 'public.plain-text']
    oPanel = NSOpenPanel.openPanel()

    oPanel.setFloatingPanel_(YES)

    oPanel.setFrame_display_(NSMakeRect(0, 0, 720, 525), YES)
    oPanel.setMessage_(msg)

    oPanel.setTitle_(title)
    oPanel.setPrompt_('Select')
    oPanel.setAllowedFileTypes_(file_types)
    oPanel.setCanCreateDirectories_(NO)
    oPanel.setCanChooseDirectories_(YES)
    oPanel.setCanChooseFiles_(YES)
    oPanel.setAllowsMultipleSelection_(YES)
    oPanel.setDirectoryURL_(NSURL.fileURLWithPath_isDirectory_(the_folder, YES))


    if oPanel.runModal() == NSFileHandlingPanelOKButton:

    return oPanel.URLs()

    else:

    return 'None'

    def main():
    names_csv = ''
    img_folder = ''
    if not len(sys.argv) == 2:
    sys.exit('There was a problem with program arguments')
    default_folder = os.path.expanduser(sys.argv[1])
    # returns a tuple of NSURL file schema for CSV file and Image Folder

    ret = get_files(default_folder)
    if not len(ret) == 2:
    sys.exit('You must select a CSV file, and an Image Folder')

    # transform tuple NSURL file format into POSIX paths
    names_csv, img_folder = [ret[i].path().encode('utf-8') for i in range(len(ret))]
    if not (os.path.isfile(names_csv) and os.path.isdir(img_folder)):
    sys.exit('Input file/folder issue')
    if not os.listdir(img_folder):
    sys.exit('directory is empty')
    # no .DS_Store files
    flist = sorted(filter(lambda f: not f.startswith('.'), os.listdir(img_folder)))

    t = ()
    # place each CSV row into a tuple of tuples
    # (('14001', '14001-name-description-mim'), (etc), ...)

    with open(names_csv, 'rb') as xcsv:
    # automatically detect csv delimiter in CSV if in csvdelim
    try:
    dialect = csv.Sniffer().sniff(xcsv.read(), delimiters=csvdelim)
    except (ValueError, csv.Error) as e:
    sys.exit('{} - {}'.format(str(type(e)), str(e.args)))
    xcsv.seek(0)
    thedata = csv.reader(xcsv, dialect)
    for row in thedata:
    t += ((row[0], row[1],),)
    newfiles = [reduce(lambda s, kv: s.replace(*kv), t, x) for x in flist]
    os.chdir(img_folder)
    for i in range(0, len(flist)):

    os.rename(flist[i], newfiles[i])


    if __name__ == '__main__':

    sys.exit(main())

    EOF"

    end rename_files

    on error_handler(nbr, msg)

    tell application "System Events"

    display alert "[ " & nbr & " ] " & msgascriticalgiving up after 10

    end tell

    return

    end error_handler

    Nov 25, 2016 9:22 PM in response to MeatWork@Adam

    How about something like this?


    set FileNametoMod to (choose file with prompt "select a file") as alias -- this is just to test that the code snippet works. You would replace this with the filename.


    tell application "Finder"

    set theExtension to name extension of FileNametoMod

    set theFullFileName to name of FileNametoMod

    end tell


    set TheExtensionCount to ((countcharacters of theExtension) + 1) as integer-- +1 catches the dot preceding the extension. Or adjust it to catch the last few numbers, if it's a set number of digits like 0001, 0002, etc.

    set theNameCount to ((countcharacters of theFullFileName) + 1) as integer


    if theNameCount - TheExtensionCount < 5 then

    set TheShortName to theFullFileName

    else

    set TheShortName to ((characters 1 through 5 of theFullFileName) as text) & "." & theExtension

    end if


    TheShortName--is the result

    Nov 25, 2016 9:46 PM in response to Joey Delli Gatti

    I may be misunderstanding what you're asking. If you're trying to take a list of files in a folder, and rename them, and then save the old and new names side-by-side to a comma-delimited file, then I think my way is helpful. If you're just going into a cvs file and trying to create new names for your files, then it seems like dropping the cvs file into Excel and doing a quick formula like this would work, and would be way quicker than writing a script:


    =IF(SEARCH(".",A1,1)<5, LEFT(A1, SEARCH(".",A1,1)-1), LEFT(A1,5))&[some numbering system you want]&RIGHT(A1,LEN(A1)-SEARCH(".",A1,1))


    Drag the formula from cell B1 down to the bottom row of data. Then go to the "Save" dialog and save as a CSV file.

    Nov 25, 2016 9:51 PM in response to VikingOSX

    The following syntax is designed to ensure that the saved, standalone application always quits when it is done. A running script will not allow you to tell it to quit, and you get a nagging message at the end of the script, when you use just this syntax:


    tell current application to if it is running then quit


    A standalone, saved and running AppleScript application has a process name of "applet", and the following code tests for it. Thus, no nagging messages from the Script Editor, and the applet will silently quit itself at on conclusion.


    In the code that I just posted, replace the above AppleScript, with the following code:


    tell application "System Events"

    -- applet won't exist unless running as saved, AppleScript application

    set processExists to exists process "applet"

    end tell

    if processExists then tell current application to if it is running then quit

    Nov 25, 2016 9:53 PM in response to Joey Delli Gatti

    Too late to edit my above post with the AppleScript code snippet.


    I was going to say that theFullFileName is the "before" name, and the TheShortName is the "after" name.


    All you'd have to do is loop through that logic with theFullFileName1, theFullFileName2, etc, and do the same with the short names, create another snippet of code at the end that pastes all of the results into a text file renamed as "[filename].csv".

    This thread has been closed by the system or the community team. You may vote for any posts you find helpful, or search the Community for additional answers.

    Using Applescript & CSV to partial rename files

    Welcome to Apple Support Community
    A forum where Apple customers help each other with their products. Get started with your Apple Account.