Sunday, February 25, 2018

#1 2008-06-20 01:44:20 am

Martin Michel
Administrator
From:: Berlin, Germany
Registered: 2008-03-03
Posts: 701
Website

Thoughts and examples about using the quartzfilter tool

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:



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.

Applescript:


- 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


Plonum - Convert images to iWork Numbers documents


Filed under: PDF, Quartz, quartzfilter, filter

Offline

 

#2 2013-10-17 08:11:43 am

macmadness86
Member
Registered: 2012-01-28
Posts: 13

Re: Thoughts and examples about using the quartzfilter tool

Your hidden command seems to be missing in OS X Mountain Lion 10.8.5.
sad

Offline

 

#3 2014-01-10 04:13:52 pm

Don
Member
Registered: 2006-01-25
Posts: 51

Re: Thoughts and examples about using the quartzfilter tool

I would like to make a script that reduces the pixel size of images in PDF files using the built in OS X pdf engine.

This thread, and my own research, seem to indicate that something like this used to be possible from the OS X command line, but appears to no longer be there in 10.8 and above.

Any help would be appreciated.

Offline

 

#4 2014-01-10 05:24:03 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5243

Re: Thoughts and examples about using the quartzfilter tool

If you're running 10.9, you can use an AppleScriptObjC-based library to apply a QuartzFilter. Save this as an ASObjC-based .scptd file in your Script Libraries folder:

Applescript:

use framework "Foundation"
use framework "Quartz"

on filterPDF:pathToPDF newPath:newPDFPath
   set filterPath to current application's NSString's stringWithString:"/System/Library/Filters/Reduce File Size.qfilter"
   set filterURL to current application's NSURL's fileURLWithPath:filterPath
   set theFilter to current application's QuartzFilter's quartzFilterWithURL:filterURL
   set pdfURL to current application's NSURL's fileURLWithPath:pathToPDF
   set thePDFDoc to current application's PDFDocument's alloc()'s initWithURL:pdfURL
   set theOptions to {QuartzFilter:theFilter}
   thePDFDoc's writeToFile:newPDFPath withOptions:theOptions
end filterPDF:newPath:

Change the filter path to suit, or use the script above to create new versions of the filter, then call the script like this:

Applescript:

use theLib : script "<name of lib file>"

theLib's filterPDF:"/Users/shane/Desktop/Everyday AppleScriptObjC.pdf" newPath:"/Users/shane/Desktop/Everyday AppleScriptObjC-2.pdf"


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#5 2014-01-13 10:29:55 am

Don
Member
Registered: 2006-01-25
Posts: 51

Re: Thoughts and examples about using the quartzfilter tool

I am sorry, I am unfamiliar with the variable:thisVarible structure you use in this code sample.

I did purchase your overview book on ASObjC, is there a few relevant pages I can go to read up on how to underhand this?

Thanks so much for your help.

Don

Last edited by Don (2014-01-13 10:32:46 am)

Offline

 

#6 2014-01-13 04:18:41 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5243

Re: Thoughts and examples about using the quartzfilter tool

Don wrote:

I am sorry, I am unfamiliar with the variable:thisVarible structure you use in this code sample.


It's known as interleaved syntax, and it was introduced in Mavericks. If you write this:

Applescript:

on doSomething_toSomething_(someArg, otherArg)

in 10.9 it will compile as:

Applescript:

on doSomething:someArg toSomething:otherArg

It's just a different way of writing the same thing.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#7 2014-01-14 04:19:52 pm

Don
Member
Registered: 2006-01-25
Posts: 51

Re: Thoughts and examples about using the quartzfilter tool

I found this tutorial which was very helpful on exactly how to deal with the Script Libraries folder, and was able to get everything in place.

http://macscripter.net/viewtopic.php?id=41638

Now, when I run a pdf though this, I get back false as the result, and no pdf is made.

I played around with the script in ASObjC Explorer 2 and got this error:

17:14:10.790 Can’t get POSIX path of class "ObjectWithFords". (-1728)
17:14:10.790 -- End -- (~0.011s)

Last edited by Don (2014-01-14 04:20:46 pm)

Offline

 

#8 2014-01-14 04:58:12 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5243

Re: Thoughts and examples about using the quartzfilter tool

I'm not sure how ObjectWithFords gets involved. I just tried it in ASObjC Explorer 2 (without the use statements and as a normal handler):

Applescript:

0000.000 -- Start --
0000.001 [2] set filterPath to current application's NSString's stringWithString:"/System/Library/Filters/Reduce File Size.qfilter"
--> /System/Library/Filters/Reduce File Size.qfilter
0000.008 [3] set filterURL to current application's NSURL's fileURLWithPath:filterPath
--> file://localhost/System/Library/Filters/Reduce%20File%20Size.qfilter
0000.009 [4] set theFilter to current application's QuartzFilter's quartzFilterWithURL:filterURL
--> QuartzFilter:0x40010c020 private:0x400ea4100 name:Reduce File Size filter:0x40010c020 owner:0x0 URL:0x40037fbc0 properties:0x4011b42e0 qf:0x0 dict:0x0
0000.010 [5] set pdfURL to current application's NSURL's fileURLWithPath:pathToPDF
--> file://localhost/Users/shane/Desktop/Everyday%20AppleScriptObjC.pdf
0000.011 [6] set thePDFDoc to current application's PDFDocument's alloc()'s initWithURL:pdfURL
--> PDFDocument, number of pages: 85

0000.012 [7] set theOptions to {QuartzFilter:theFilter}
--> {QuartzFilter:«class ocid» id «data optr0000000020C0100004000000»}
0001.239 [8] thePDFDoc's writeToFile:newPDFPath withOptions:theOptions
--> 1
0001.240 [11] my filterPDF:"/Users/shane/Desktop/Everyday AppleScriptObjC.pdf" newPath:"/Users/shane/Desktop/Everyday AppleScriptObjC-2.pdf"
--> 1
0001.241 -- End -- (~1.241s)


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#9 2014-04-29 10:04:26 am

Speranza89
Member
Registered: 2014-04-29
Posts: 2

Re: Thoughts and examples about using the quartzfilter tool

Shane Stanley wrote:

If you're running 10.9, you can use an AppleScriptObjC-based library to apply a QuartzFilter. Save this as an ASObjC-based .scptd file in your Script Libraries folder:

Applescript:

use framework "Foundation"
use framework "Quartz"

on filterPDF:pathToPDF newPath:newPDFPath
   set filterPath to current application's NSString's stringWithString:"/System/Library/Filters/Reduce File Size.qfilter"
   set filterURL to current application's NSURL's fileURLWithPath:filterPath
   set theFilter to current application's QuartzFilter's quartzFilterWithURL:filterURL
   set pdfURL to current application's NSURL's fileURLWithPath:pathToPDF
   set thePDFDoc to current application's PDFDocument's alloc()'s initWithURL:pdfURL
   set theOptions to {QuartzFilter:theFilter}
   thePDFDoc's writeToFile:newPDFPath withOptions:theOptions
end filterPDF:newPath:

Change the filter path to suit, or use the script above to create new versions of the filter, then call the script like this:

Applescript:

use theLib : script "<name of lib file>"

theLib's filterPDF:"/Users/shane/Desktop/Everyday AppleScriptObjC.pdf" newPath:"/Users/shane/Desktop/Everyday AppleScriptObjC-2.pdf"


I tried to use this today but am getting error "Unrecognized function writeToFile_withOptions_." number -10000. Not sure what's going wrong as I was able to alloc/init the PDF document class and have confirmed that the path is a string and the options are a dictionary. Any ideas?

Here's the code I'm using to call the library script:

Applescript:


set inputFolder to (choose folder with prompt "Select a folder")
tell application "Finder"
   set newFolderName to "Compressed"
   if not (exists folder newFolderName of inputFolder) then
       make new folder at inputFolder with properties {name:newFolderName}
   end if
   set outputFolder to folder newFolderName of inputFolder
   set inputFiles to get every file of inputFolder as alias list
end tell

repeat with inputFile in inputFiles
   tell application "Finder" to set theName to name of inputFile
   set newPathToPDF to (outputFolder as text) & theName
   set newPathToPDF to quoted form of POSIX path of newPathToPDF
   set originalPathToPDF to quoted form of POSIX path of inputFile
   (script "ReduceFileSize2"'s filterPDF:originalPathToPDF newPath:newPathToPDF)
end repeat

Offline

 

#10 2014-04-29 05:39:48 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5243

Re: Thoughts and examples about using the quartzfilter tool

Remove the "quoted form of" each time -- you only need that for shell scripts.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

#11 2014-04-30 07:18:28 am

Speranza89
Member
Registered: 2014-04-29
Posts: 2

Re: Thoughts and examples about using the quartzfilter tool

Thanks - worked like a charm.

I was also getting error 4960 occasionally, but discovered from another of your posts that it was because of a bug in applescript and should only happen on the first compile.

Thanks again.

Offline

 

#12 2014-04-30 07:20:05 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5243

Re: Thoughts and examples about using the quartzfilter tool

Speranza89 wrote:

I was also getting error 4960 occasionally, but discovered from another of your posts that it was because of a bug in applescript


FWIW, it's actually a bug in AppleScript Editor; if you use another editor (Script Debugger or my ASObjC Explorer) it doesn't happen.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)