Apple Event: May 7th at 7 am PT

Looks like no one’s replied in a while. To start the conversation again, simply ask a new question.

How do I make this script faster?

Hi there!


I made my very first ever script in order to create several thousand QR Codes for my boss. The script works (most of the time) but the problem I’m having is the speed. The script takes 4 seconds to create one code, which is fine except for the fact that we will need to create 75,000 codes at one time. This comes out to a total of 83 hours to create them all! I have a few delays in my script and I have tried taking them out and shortening them but any more alterations causes the script to not work correctly. 😟 I’m sure that the script will look super newbish to pros like you guys but any suggestions would be grand.


The script:


on adding folder items tothis_folderafter receivingthese_items

repeat with an_item in these_items

tell application "TextEdit"


activate


openan_item


tell application "TextEdit"


activate

end tell

tell application "System Events"


keystroke "a" usingcommand down

end tell


tell application "System Events"


keystroke "c" usingcommand down

end tell


tell application "System Events"


keystroke "w" usingcommand down

end tell

delay 0.25

tell application "System Events"


keystroke "q" usingcommand down

end tell

delay 0.25

tell application "System Events"


keystroke "v" usingcommand down

end tell


tell application "System Events" to keystroke return

delay 0.25


tell application "Preview"


activate

end tell

delay 0.25

tell application "System Events"


keystroke "s" usingcommand down

end tell

delay 0.25

tell application "Preview"


activate

end tell

delay 0.75

tell application "System Events" to keystroke return


delay 0.5

tell application "System Events" to keystroke return

delay 0.25

tell application "System Events"


keystroke "w" usingcommand down

delay 0.25

end tell

end tell

end repeat

end adding folder items to





How it works:

The script relays on a couple factors. The “command q” shortcut triggers another script that makes the QR code, which I did not write:



set the theRes to "300"


display dialog "URL?" default answer "http://www.google.com"

set the theURL to the text returned of the result


set input to "http://chart.apis.google.com/chart?cht=qr&chs=" & theRes & "x" & theRes & "&chl=" & theURL


set input to quoted form of input


set temp_file to (path totemporary items)

set temp_name to do shell script "uuidgen"

set temp_file to (POSIX path of temp_file) & temp_name

set q_temp_file to quoted form of temp_file


set cmd to "curl -o " & q_temp_file & " " & input


do shell scriptcmd

set x to alias (POSIX filetemp_file)

tell application "Finder" to open x



My script works with this one using a text file that is placed in a folder with the script linked to it. I hope this is making sense, I’m sure it seems backwards and weird. To make a long story short, I need to somehow make this work faster and I am willing to rework the entire thing or go with a new idea entirely. Thanks in advance!


-Erin

iMac, OS X Mavericks (10.9.2)

Posted on Apr 17, 2014 4:46 PM

Reply
28 replies

Apr 18, 2014 3:38 PM in response to Frank Caggiano

Yay, it works! At least, it creates the code much faster. I am wondering, now, if there is a better way to save and close the files. The folder I am saving them to is called QR Codes. I feel like the saving and closing part is the slowest part. Also a naming convention of any kind would be great, even just part of the URL or numerical. 🙂

Apr 18, 2014 4:29 PM in response to birdmadgirl7

Okay, I've gotten this so far:



on adding folder items tothis_folderafter receivingthese_items


repeat with an_item in these_items


-- read the file contents:


-- (this one line replaces the entire 'tell application "TextExit" stuff

set theURL to read file (an_item as text)



-- setup the API call:


set theRes to "300"


set input to "http://chart.apis.google.com/chart?cht=qr&chs=" & theRes & "x" & theRes & "&chl=" & theURL

set input to quoted form of input



-- there may be a better way of doing this, but if this is working for you, go with it:


set temp_file to (path totemporary items)

set temp_name to do shell script "uuidgen"

set temp_file to (POSIX path of temp_file) & temp_name

set q_temp_file to quoted form of temp_file


set cmd to "curl -o " & q_temp_file & " " & input



-- go make the API call and save the resulting file



do shell scriptcmd



-- this code here seems irrelevant, but if it works for you...

set x to alias (POSIX filetemp_file)

tell application "Finder" to open x



tell application "Preview"

save document 1 in POSIX file "/Users/user/Desktop/QR Codes/QRcode.png"

end tell



end repeat


end adding folder items to




So, this is much faster but two things need to be added and I am unsure of how to do so:


1. This saves only one code with the same name, so subsequent codes just replace the previous. Not good. 😟


2. The files need to close. Doing this:


tell application "System Events"


keystroke "w" usingcommand down

end tell

Is all I can really come up with. Any thoughts? And thanks for all your help so far!

Apr 18, 2014 6:48 PM in response to birdmadgirl7

Ok this revised script:



(*

Make sure the folder Desktop:qcodes exist before running the script

That is where the output files will be placed

*)


on adding folder items tothis_folderafter receivingthese_items


repeat with an_item in these_items


-- read the file contents:


-- (this one line replaces the entire 'tell application "TextExit" stuff



set theURL to readan_item


-- setup the API call:


set theRes to "300"


set input to "http://chart.apis.google.com/chart?cht=qr&chs=" & theRes & "x" & theRes & "&chl=" & theURL

set input to quoted form of input




-- there may be a better way of doing this, but if this is working for you, go with it:

set temp_file to (path to desktop as text)

set temp_name to do shell script "uuidgen"


set temp_file to (POSIX path of temp_file) & "qcodes/" & temp_name

set q_temp_file to quoted form of temp_file


set cmd to "curl -o " & q_temp_file & " " & input



-- go make the API call and save the resulting file



do shell scriptcmd



-- this code here seems irrelevant, but if it works for you...


(*

set x to alias (POSIX file temp_file)

display dialog x as text

tell application "Finder" to open x

*)

end repeat


will place the output qcode files in a folder in your Desktop called qcodes. Each file will be unique named by the UUID gotten in the shell cal to uuidgen.


Note the output files could be named something else more meaningful if you have something, some part of the URL or something else. Also the folder they are placed in could be changed (both the name and the location)


In the above code all the lines between (* and *) are comments. I commented some of the original code rather then remove it for now until you know this works.


Also as stated at the top of the app you need to make the folder qcodes in your Desktop before running this.


regards

Apr 19, 2014 2:52 AM in response to birdmadgirl7

Hello


You may create a Folder Action script using the following code.


I revised the create_QR_code_for_file() handler so that it now logs errors in a log file in target directory.


Regards,

H




property outdir : (path to desktop)'s POSIX path & "QR Codes"

on adding folder items to d after receiving aa
    set donedir to (d's POSIX path) & "_DONE"
    repeat with a in aa
        set ret to create_QR_code_for_file(a's POSIX path, 300, outdir)
        if ret = 0 then
            move_file_to_directory(a's POSIX path, donedir)
        end if
    end repeat
end adding folder items to


on create_QR_code_for_file(infile, res, outdir)
    (*
        string infile : POSIX path of input text file whose contents is to be converted to QR code
        integer res : resolution of resulting png
        string outdir : POSIX path of output directory
        
        * Data in infile is assumed to be UTF-8 text
        * Given infile = a.txt, output png file is saved as outdir/a.png
    *)
    set args to ""
    repeat with a in {infile, res, outdir}
        set args to args & ("" & a)'s quoted form & space
    end repeat
    do shell script "/bin/bash -s <<'EOF' - " & args & "
infile=\"$1\"    # source plain text file
res=\"$2\"        # resolution of resulting png
outdir=\"$3\"    # output directory

[[ -d \"$outdir\" ]] || mkdir -p \"$outdir\" || exit    # create output directory if not present
name=${infile##*/}                        # input file name
outfile=\"$outdir/${name%.*}.png\"        # output png file
errlog=\"$outdir/$(date +'%F_QR_code_generator_error_log.txt')\"    # error log file per date

# redirect stderr to error log file and add timestamp to each entry
exec 2> >(while read e
do
    printf '%-25s %s\\n' \"$(date +'%F %T%z')\" \"$e\" 
done >> \"$errlog\")

# uri escape via perl
str_e=$(perl -CSDA -MURI::Escape -0777 -ne 'print uri_escape_utf8($_)' \"$infile\")
[[ -z \"$str_e\" ]] && { echo \"Zero length data from $infile.\" >&2; echo 1000; exit; }

# call google api via curl
req=\"http://chart.apis.google.com/chart?cht=qr&chs=${res}x${res}&chl=${str_e}\"
curl -sS -o \"$outfile\" \"$req\"
err=$?; [[ $err -ne 0 ]] && { echo \"Failed to call google chart api for $infile.\" >&2; exit; }

# check the result file
if [[ $(file -I \"$outfile\") =~ 'image/png;' ]]
then
    echo 0
else
    errfile=\"$outfile.error.html\"
    mv -f \"$outfile\" \"$errfile\"        # rename x.png to x.png.error.html
    echo \"Failed to generate QR code for $infile: see $errfile.\" >&2
    echo 1000
fi

# remove empty errlog if present
# use sync(1) to flush buffers in advance, otherwise this may delete non-empty error log
sync
[[ -e \"$errlog\" && -z $(<\"$errlog\") ]] && rm \"$errlog\"

exit 0
EOF"
    result as number
end create_QR_code_for_file


on move_file_to_directory(f, d)
    do shell script "dst=" & d's quoted form & "; f=" & f's quoted form & "
[[ -d \"$dst\" ]] || mkdir -p \"$dst\" || exit
mv -f \"$f\" \"$dst\""
end move_file_to_directory

Apr 19, 2014 7:21 AM in response to birdmadgirl7

Hello


By the way, I just wonder why you're feeding every text (URL, I presume) via individual text file. Is there any reason for that? I ask this because if you have list of URLs and corresponding name of QR code PNG files, you can generate the QR codes directly from the list. No need for individual text files and no need for Folder Action at all.


If my assumption is correct, all we need for input is the list of URLs and corresponding QR code file names. Tab delimited plain text will suffice.


E.g.,


URL 1 [tab] name 1
URL 2 [tab] name 2
...


or


name 1 [tab] URL 1
name 2 [tab] URL 2
...


Regards,

H

Apr 19, 2014 9:38 PM in response to birdmadgirl7

Hello


Since I will be busy and unable to respond often next week, here I post another version to generate QR codes based upon a TSV file.


TSV file (UTF-8 plain text file) consists of records of two fields, that are name and url, separated by tab character. E.g.,


QR001    http://www.google.com
QR002    https://www.google.com/search?as_q=QR+code
QR003    https://www.google.com/search?as_q=google+chart+api
QR004    https://developers.google.com/chart/
QR005    https://developers.google.com/chart/infographics/docs/qr_codes


* Note that the space(s) between name and url is actually one TAB character. (Fora software will replace TAB by spaces in this post.)


You may copy the code below into new window of AppleScript Editor and run it. It will ask you to choose TSV file and destination directory and then genenate QR codes in destination directory. E.g.,


QR001.png
QR002.png
QR003.png
QR004.png
QR005.png


each of which represents the corresponding url in TSV file.


If you have data in Excel sheet, you may arrange name and url columns in this order, select them, copy and paste it into new plain text document, which will result in the desired TSV format.


* Note that each line in TSV must be terminated by LF (U+000A LINE FEED), aka unix line ending. And the text encoding must be UTF-8.


Good luck,

H



(*
    Create QR codes as defined in a TSV file

    * TSV file consists of records of two fields, that are name and data, such as
        name [TAB] data [LF]

    * This will generate PNG image of QR code for each data and save it as name.png in specified output directory
*)
set infile to (choose file with prompt "Choose input TSV file" of type {"public.plain-text"})'s POSIX path
set outdir to (choose folder with prompt "Choose output directory")'s POSIX path
create_QR_codes(infile, 300, outdir)

on create_QR_codes(infile, res, outdir)
    (*
        string infile : POSIX path of input TSV file. Each entry = name [TAB] data [LF]
        integer res : resolution of resulting png
        string outdir : POSIX path of output directory

        * Text in infile is assumed to be in UTF-8
        * For each record = name [TAB] data [LF],  QR code for data is saved as outdir/name.png
        * Error is logged in error log file outdir/YYYY-MM-DD_QR_code_generator_error_log.txt
    *)
    set args to ""
    repeat with a in {infile, res, outdir}
        set args to args & ("" & a)'s quoted form & space
    end repeat
    do shell script "/bin/bash -s <<'EOF' - " & args & "
infile=\"$1\"    # input tsv file. each entry consists of name [TAB] data [LF]
res=\"$2\"        # resolution of resulting png
outdir=\"$3\"    # output directory

(( ${#outdir} > 1 )) && outdir=\"${outdir%/}\"            # remove redundant trailing / if any
[[ -d \"$outdir\" ]] || mkdir -p \"$outdir\" || exit    # create output directory if not present
errlog=$outdir/$(date +'%F_QR_code_generator_error_log.txt')    # error log file per date in outdir

# redirect stderr to error log file and add timestamp to each line
exec 2> >(while read e
do
    printf '%-25s %s\\n' \"$(date +'%F %T%z')\" \"$e\" 
done >> \"$errlog\")

# process each tsv record
while read e
do
    IFS=$'\\t' read name data <<< \"$e\"
    [[ -z $name || -z $data ]] && continue
    outfile=\"$outdir/${name}.png\"    # output png file

    # uri escape via perl
    str_e=$(perl -CSDA -MURI::Escape -e 'print uri_escape_utf8($ARGV[0])' \"$data\")
    [[ -z \"$str_e\" ]] && { echo \"Zero length data for $name {$data}.\" >&2; continue; }

    # call google api via curl
    req=\"http://chart.apis.google.com/chart?cht=qr&chs=${res}x${res}&chl=${str_e}\"
    curl -sS -o \"$outfile\" \"$req\"
    err=$?; [[ $err -ne 0 ]] && { echo \"Failed to call google chart api for $name {$data}.\" >&2; continue; }

    # check the result file
    if [[ ! $(file -I \"$outfile\") =~ 'image/png;' ]]
    then
        errfile=\"$outfile.error.html\"
        mv -f \"$outfile\" \"$errfile\"        # rename x.png to x.png.error.html
        echo \"Failed to generate QR code for $name {$data}: see $errfile.\" >&2
    fi
done < $infile

# remove empty errlog if present
# use sync(1) to flush buffers in advance, otherwise this may delete non-empty error log
sync
[[ -e \"$errlog\" && -z $(<\"$errlog\") ]] && rm \"$errlog\"

exit 0
EOF"
end create_QR_codes


Message was edited by: Hiroto (fixed typo: U+00A0 => U+000A)

Apr 20, 2014 12:24 AM in response to Hiroto

Oops. I forgot to quote the $infile in shell script.


Here's a corrected version, which also handles the last line not terminated by LF.


In brief, it replaced


while read e
do
:
done < $infile


with


while read e
do
:
done < <(awk '1' "$infile")


in shell script.


The entire code of revised AppleScript script is as follows.


(*
    Create QR codes as defined in a TSV file
    
    * TSV file consists of records of two fields, that are name and data, such as
        name [TAB] data [LF]
    
    * This will generate PNG image of QR code for each data and save it as name.png in specified output directory
*)

set infile to (choose file with prompt "Choose input TSV file" of type {"public.plain-text"})'s POSIX path
set outdir to (choose folder with prompt "Choose output directory")'s POSIX path
create_QR_codes(infile, 300, outdir)

on create_QR_codes(infile, res, outdir)
    (*
        string infile : POSIX path of input TSV file. Each entry = name [TAB] data [LF]
        integer res : resolution of resulting png
        string outdir : POSIX path of output directory
        
        * Text in infile is assumed to be in UTF-8
        * For each record = name [TAB] data [LF],  QR code for data is saved as outdir/name.png
        * Error is logged in error log file outdir/YYYY-MM-DD_QR_code_generator_error_log.txt
    *)
    set args to ""
    repeat with a in {infile, res, outdir}
        set args to args & ("" & a)'s quoted form & space
    end repeat
    do shell script "/bin/bash -s <<'EOF' - " & args & "
infile=\"$1\"    # input tsv file. each entry consists of name [TAB] data [LF]
res=\"$2\"        # resolution of resulting png
outdir=\"$3\"    # output directory

(( ${#outdir} > 1 )) && outdir=\"${outdir%/}\"            # remove redundant trailing / if any
[[ -d \"$outdir\" ]] || mkdir -p \"$outdir\" || exit    # create output directory if not present
errlog=$outdir/$(date +'%F_QR_code_generator_error_log.txt')    # error log file per date in outdir

# redirect stderr to error log file and add timestamp to each line
exec 2> >(while read e
do
    printf '%-25s %s\\n' \"$(date +'%F %T%z')\" \"$e\" 
done >> \"$errlog\")

# process each tsv record
while read e
do
    IFS=$'\\t' read name data <<< \"$e\"
    [[ -z $name || -z $data ]] && continue
    outfile=\"$outdir/${name%.*}.png\"    # output png file

    # uri escape via perl
    str_e=$(perl -CSDA -MURI::Escape -e 'print uri_escape_utf8($ARGV[0])' \"$data\")
    [[ -z \"$str_e\" ]] && { echo \"Zero length data for $name {$data}.\" >&2; continue; }
    
    # call google api via curl
    req=\"http://chart.apis.google.com/chart?cht=qr&chs=${res}x${res}&chl=${str_e}\"
    curl -sS -o \"$outfile\" \"$req\"
    err=$?; [[ $err -ne 0 ]] && { echo \"Failed to call google chart api for $name {$data}.\" >&2; continue; }
    
    # check the result file
    if [[ ! $(file -I \"$outfile\") =~ 'image/png;' ]]
    then
        errfile=\"$outfile.error.html\"
        mv -f \"$outfile\" \"$errfile\"        # rename x.png to x.png.error.html
        echo \"Failed to generate QR code for $name {$data}: see $errfile.\" >&2
    fi
done < <(awk '1' \"$infile\")

# remove empty errlog if present
# use sync(1) to flush buffers in advance, otherwise this may delete non-empty error log
sync
[[ -e \"$errlog\" && -z $(<\"$errlog\") ]] && rm \"$errlog\"

exit 0
EOF"
end create_QR_codes


Cheers,

H

How do I make this script faster?

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