As mentioned here before the quartzfilter tool hidden inside of Mac OS X can be of real help when it comes to PDF manipulation on the command line. It allows you to apply custom quartz filters created with the ColorSync Utility to your PDF or image files. Those quartz filters in turn can perform many useful tasks. They can reduce the file size by image compression, convert color to gray scale, increase lightness or add comments, just to mention a few possibilities.
The quartzfilter tool uses the following syntax:
/System/Library/Printers/Libraries/./quartzfilter inputfile filterpath outputfile
Do not use the same file path for the in- and output file, as this will result in an error. They must be different.
The example below would apply the «Sepia Tone» quartz filter located in /System/Library/Filters/ to a PDF file named «qfin.pdf» on my desktop and save the result in a PDF file named «qfout.pdf». The path to the quartz filter is quoted as it contains spaces:
/System/Library/Printers/Libraries/./quartzfilter /Users/martin/Desktop/qfin.pdf ‘/System/Library/Filters/Sepia Tone.qfilter’ /Users/martin/Desktop/qfout.pdf
Preinstalled quartz filters are located in /System/Library/Filters/, but your own custom quartz filters created with the ColorSync Utility are saved in your library folder under /Users/yourname/Library/Filters/.
So the quartzfilter tool let’s you apply preinstalled and your own custom quartz filters. Fine.
But what if I want to write a neat little AppleScript droplet for the Mac community, that would utilize the quartzfilter tool to batch-shrink countless dropped PDF or image files by a user-chosen image compression factor?
Which leads directly to the questions: What exactly is a quartz filter and can an application/script create its own custom quartz filters on-the-fly? It would be just useless/too error-prone to ask the user to create the custom quartz filter in ColorSync Utility for us first…
Unfortunately the ColorSync Utility itself is not scriptable with AppleScript, so this way for automatically creating custom quartz filters is blocked. But opening the «Reduce File Size» quartz filter file with my favourite free text editor - TextWrangler - revealed a surprise:
Quartz filters (or better: quartz filer files) are just pure XML/PLIST files!
This means that an application or script can easily create its own custom quartz filters using an XML library or/and a template engine with default quartz filters. Even vanilla AppleScript can do it with the XML suite of the System Events Scripting Addition.
Therefor a PDF or image processing droplet could contain a copy of the above mentioned «Reduce File Size» quartz filter and change the value for the XML element «Compression Quality» according to a user-chosen factor. This way you could quickly batch-shrink PDF or image files with custom settings!
Just in case you are not yet convinced I invite you to download, test and inspect the sample droplet script below, which reduces the file size of dropped PDF documents according to a chosen compression quality:
Quartz-O-Shrink - Conveniently batch-shrink dropped PDF documents by a custom compression quality (ca. 41.2 KB)
The AppleScript was tested under Mac OS X 10.5.3 with Intel and PowerPC based Macs.
Important: Opening and saving the below script code in Script Editor won’t result in a usable AppleScript! That is because the AppleScript internally relies on a quartz filter template, which is located inside its Application bundle. Therefor please download the complete script here.
- created: 19.06.2008
-- tested on/with:
-- ¢ Intel & PowerPC based Macs
-- ¢ Mac OS X 10.5.3
-- This AppleScript reduces the file size of dropped PDF documents according to
-- a chosen compression quality. The script actually shrinks the PDF
-- documents by applying a custom quartz filter.
property mytitle : "Quartz-O-Shrink"
-- I am called when the user drops Finder items onto the script's icon
on open dropitems
try
-- searching the dropped Finder items for PDF documents
-- PDF documents are identified by a '*.pdf' name extension
set pdffilepaths to {}
set pdffilenames to {}
repeat with dropitem in dropitems
set iteminfo to info for dropitem
if name extension of iteminfo is in {"pdf"} then
set pdffilepaths to pdffilepaths & (dropitem as Unicode text)
set pdffilenames to pdffilenames & (name of iteminfo)
end if
end repeat
-- no PDF documents found
if pdffilepaths is {} then
set errmsg to "You did not drop any PDF files onto me."
my dsperrmsg(errmsg, "--")
return
end if
-- asking the user to choose a compression quality
set compqual to my choosecompqual()
if compqual is missing value then
return
end if
-- asking the user to choose an output folder where
-- the shrinked PDF files are saved
set outputfolderpath to my chooseoutputfolder()
if outputfolderpath is missing value then
return
end if
-- creating the custom quartz filter with the chosen compression quality
set quartzfilterpath to my createquartzfilter(compqual)
if quartzfilterpath is missing value then
return
end if
-- applying the custom quartz filter to the dropped PDF documents
set countpdffilepaths to length of pdffilepaths
repeat with i from 1 to countpdffilepaths
set pdffilepath to item i of pdffilepaths
set pdffilename to item i of pdffilenames
set unusedfilepath to my getunusedfilepath(outputfolderpath, pdffilename)
set command to "/System/Library/Printers/Libraries/./quartzfilter " & quoted form of (POSIX path of pdffilepath) & space & quoted form of (POSIX path of quartzfilterpath) & space & quoted form of (POSIX path of unusedfilepath)
do shell script command
end repeat
on error errmsg number errnum
my dsperrmsg(errmsg, errnum)
end try
end open
-- I am called when the user opens the script with a doubleclick
on run
set welcomemsg to "I am an AppleScript droplet which reduces the file size of PDF documents." & return & return & "Therefor please drag and drop a bunch of PDF documents onto my icon."
tell me
activate
display dialog welcomemsg buttons {"OK"} default button 1 with title mytitle with icon note
end tell
end run
-- I am asking the user to choose a compression quality
on choosecompqual()
set compquals to {"Best » low compression, high image quality", "1.0", "0.9", "0.8", "0.7", "0.6", "0.5", "0.4", "0.3", "0.2", "0.1", "0.0", "Least » high compression, low image quality"}
choose from list compquals with prompt "Please choose a compression quality:" OK button name "Select" with title mytitle without multiple selections allowed and empty selection allowed
set usrchoice to result
if usrchoice is not false then
set chosencompqual to item 1 of (usrchoice as list)
if chosencompqual begins with "Best" or chosencompqual begins with "Least" then
my choosecompqual()
else
return chosencompqual
end if
else
return missing value
end if
end choosecompqual
-- I am asking the user to specify an output folder for the shrinked PDF documents
-- I return the folder path or «missing value» in case the user cancels the dialog
on chooseoutputfolder()
try
set chosenfolder to choose folder with prompt "Please specify an output folder for the processed files:" without multiple selections allowed, showing package contents and invisibles
return (chosenfolder as Unicode text)
on error
return missing value
end try
end chooseoutputfolder
-- I am creating a new quartz filter containing the user chosen compression quality
-- I return the file path of the created quartz filter
on createquartzfilter(compqual)
-- path to the quartz filter template located in the script bundle
set tmpquartzfilterpath to (((path to me) as Unicode text) & "Contents:Resources:Reduce File Size.qfilter")
set filecont to my readfromfile(tmpquartzfilterpath)
-- reading the content of the quartz filter template failed :(
if filecont is false then
set errmsg to "Could not read the content of the quartz filter template supposed to be located at:" & return & return & tmpquartzfilterpath
error errmsg
end if
-- replacing the placeholder with the chosen compression quality
set newfilecont to my searchnreplace("$compqual$", compqual, filecont)
-- creating our application support folder if it does not already exist
set appsuppfolderpath to (((path to application support folder from user domain) as Unicode text) & mytitle & ":")
try
set appsuppfolderalias to appsuppfolderpath as alias
on error
do shell script "mkdir -p " & quoted form of (POSIX path of appsuppfolderpath)
end try
-- path to the new quartz filter containing the chosen compression quality
set newquartzfilterpath to appsuppfolderpath & "Reduce File Size.qfilter"
-- writing the modified template content to the new file path
set writesuccess to my writetofile(newfilecont, newquartzfilterpath)
if not writesuccess then
set errmsg to "Could not write to file at:" & return & return & newquartzfilterpath
error errmsg
end if
-- returning the file path to the newly created quartz filter
return newquartzfilterpath
end createquartzfilter
-- I am returning an unused file path
-- example:
-- destfolderpath: «DandyDisk:Users:bender:Desktop:icnsfiles:»
-- filename: «example.pdf»
-- -> filepath: «DandyDisk:Users:bender:Desktop:icnsfiles:example.png»
-- if this filepath already exists:
-- -> filepath: «DandyDisk:Users:bender:Desktop:icnsfiles:example 1.png»
-- and so on...
on getunusedfilepath(destfolderpath, filename)
set counter to 0
repeat
if counter < 1 then
set unusedfilepath to destfolderpath & filename
else
set unusedfilepath to destfolderpath & ((characters 1 through -5 of filename) as Unicode text) & space & counter & ((characters -4 through -1 of filename) as Unicode text)
end if
try
set unusedfilealias to unusedfilepath as alias
on error
exit repeat
end try
set counter to counter + 1
end repeat
return unusedfilepath
end getunusedfilepath
-- I am a very old search & replace function...
on searchnreplace(searchstr, replacestr, txt)
considering case, diacriticals and punctuation
if txt contains searchstr then
set olddelims to AppleScript's text item delimiters
set AppleScript's text item delimiters to {searchstr}
set txtitems to text items of txt
set AppleScript's text item delimiters to {replacestr}
set txt to txtitems as Unicode text
set AppleScript's text item delimiters to olddelims
end if
end considering
return txt
end searchnreplace
-- I am reading and returning the content of a given file path
-- I return «false» in case the file could not be read
on readfromfile(filepath)
try
set openfile to open for access filepath
set filecont to read openfile
close access openfile
return filecont
on error
try
close access openfile
end try
return false
end try
end readfromfile
-- I am writing given content to a given file path using UTF-8 text encoding (no BOM)
-- I return «false» in case the write attempt failed
on writetofile(cont, filepath)
try
set openfile to open for access filepath with write permission
set eof of openfile to 0
-- no BOM this time...
-- set BOM_UTF8 to ((ASCII character 239) & (ASCII character 187) & (ASCII character 191))
write cont to openfile as «class utf8»
close access openfile
return true
on error
try
close access openfile
end try
return false
end try
end writetofile
-- I am displaying error messages
on dsperrmsg(errmsg, errnum)
tell me
activate
display dialog "Sorry, an error occured:" & return & return & errmsg & " (" & errnum & ")" buttons {"Never mind"} default button 1 with icon stop with title mytitle
end tell
end dsperrmsg