Wondered if you wouldn’t mind helping me improve this little utility. It’s for batch screenshots, like when you have access to some document through a poorly designed web interface and you’d rather have it as a local PDF.
Try it out, and please point out flaws that you come across. Who knows, you might actually find some use for it.
*edit: This uses mousetools and cliclick. cliclick was used because mousetools was unreliable at that point for some reason. Four separate “do shell script” commands are used because it gave strange results to send all four commands as a single shell script call.
Thanks,
Tim
(*
This script is for batch screen shots.
Written by Tim Wilson in 2013, but it's not
that great so feel free to take credit.
This script requires that cliclick and mousetools
be installed in /usr/local/bin,
and that the Applescript Objective C Runner
be installed.
cliclick: http://www.bluem.net/en/mac/cliclick/
mousetools: http://www.hamsoftengineering.com/codeSharing/MouseTools/MouseTools.html
ASObjC Runner: http://www.macosxautomation.com/applescript/apps/runner.html
*)
property Dims : ""
property ClickDims : ""
property NumPages : ""
property NextPageKeys : {¬
{KeyName:"Right Arrow", KeyCode:124}, ¬
{KeyName:"Down Arrow", KeyCode:125}, ¬
{KeyName:"Space Bar", KeyCode:49}}
on run
-- Get number of pages in document
repeat
set {NumPages, ResultButton} to {text returned, button returned} of (display dialog "Enter number of pages to be captured:" default answer NumPages with title "Number of Pages")
if ResultButton is not "OK" then return 0
try
set NumPages to NumPages as integer
exit repeat
on error
display dialog "Number of pages must be an integer greater than 0!" with title "Number of Pages" with icon stop
end try
end repeat
-- Get screen dimensions for cropping of images
display dialog "Position mouse at top-left of region to capture, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set TempDims to parseLine(TempDims, return)
set Dims to {}
repeat with i from 1 to 2
set end of Dims to item i of TempDims
end repeat
display dialog "Position mouse at bottom-right of region to capture, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set TempDims to parseLine(TempDims, return)
repeat with i from 1 to 2
set end of Dims to item i of TempDims
end repeat
-- Get key used to change pages
set KeyType to "code"
set KeyList to {}
repeat with aKey in NextPageKeys
set end of KeyList to KeyName of aKey
end repeat
set end of KeyList to "Enter Coordinates at which to left-click..."
set end of KeyList to "Manually enter key code..."
set end of KeyList to "Manually enter key stroke..."
set SelectedKey to choose from list KeyList with prompt "Select key used to switch to next page:" with title "Page Change Key" default items {beginning of KeyList} without empty selection allowed
if SelectedKey is false then
return 0
else if item 1 of SelectedKey is "Manually enter key code..." then
repeat
set PageKey to text returned of (display dialog "Enter key code to use to switch pages:" with title "Page Change Key" default answer "")
try
set PageKey to PageKey as integer
exit repeat
on error
display dialog "Key code must be integer!" with title "Page Change Key" with icon stop
end try
end repeat
else if item 1 of SelectedKey is "Manually enter key stroke..." then
repeat
set PageKey to text returned of (display dialog "Enter keystroke to use to switch pages:" with title "Page Change Key" default answer "") as string
if (count of PageKey) is 1 then
set KeyType to "stroke"
exit repeat
end if
display dialog "Key stroke must be a single character!" with title "Page Change Key" with icon stop
end repeat
else if item 1 of SelectedKey is "Enter Coordinates at which to left-click..." then
display dialog "Position mouse at the point that will be left-clicked to change the page, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set ClickDims to parseLine(TempDims, return)
set X to (item 1 of ClickDims) as integer
set Y to (item 2 of ClickDims) as integer
set KeyType to "click"
else
repeat with aKey in NextPageKeys
if item 1 of SelectedKey is KeyName of aKey then
set PageKey to KeyCode of aKey
exit repeat
end if
end repeat
end if
-- Get delay to use between page change and screen capture
set DelayList to {}
repeat with i from 0 to 30
set end of DelayList to i / 10
end repeat
set end of DelayList to "Longer..."
set SelectedDelay to choose from list DelayList with prompt "Select delay (seconds) between page change and screen capture:" with title "Screen Capture Delay" default items {beginning of DelayList} without empty selection allowed
if SelectedDelay is false then
return 0
else if item 1 of SelectedDelay is "Longer..." then
repeat
set SelectedDelay to text returned of (display dialog "Enter number of seconds to wait between screen captures:" with title "Screen Capture Delay" default answer "")
try
set SelectedDelay to SelectedDelay as integer
exit repeat
on error
display dialog "Key code must be integer!" with title "Screen Capture Delay" with icon stop
end try
end repeat
end if
-- Get folder in which to store captured images
set ImageFolder to choose folder with prompt "Select folder in which to store images:" default location (path to desktop)
tell application "ASObjC Runner"
reset progress
set properties of progress window to {name:"Batch Cropped Screenshots", button visible:true, button title:"Cancel", max value:(NumPages + 1), message:"!!!MOVE ME SO I'M NOT BLOCKING THE REGION TO CAPTURE!!!", detail:"An extra capture will be performed for redundancy", current value:(NumPages + 1)}
activate
show progress
end tell
try
display dialog "After clicking OK, you will have 10 seconds to ensure that the first page is showing and not blocked by anything except THIS window and that the selected key can change pages!!!"
on error
tell application "ASObjC Runner"
hide progress
end tell
return 0
end try
tell application "ASObjC Runner"
set properties of progress window to {detail:"Starting capture process in 10 seconds..."}
end tell
repeat with i from 1 to 100
tell application "ASObjC Runner"
set properties of progress window to {current value:i * (NumPages + 1) / 100}
if i mod 10 is 0 then
set properties of progress window to {detail:"Starting capture process in " & beginning of my parseLine((10 - (i / 10)) as string, ".") & " seconds..."}
end if
if button was pressed of progress window then
hide progress
return 0
end if
delay 0.1
end tell
end repeat
tell application "System Events"
set runningApplication to name of 1st process whose frontmost is true
end tell
tell application "Terminal" to activate
tell application "Finder" to set visible of process "Terminal" to false
repeat with i from 1 to (NumPages + 1)
tell application "ASObjC Runner"
set properties of progress window to {message:"Taking screenshots...", detail:"Page " & i & " of " & NumPages + 1 & " (" & NumPages & ")", current value:i}
if button was pressed of progress window then
hide progress
return 0
end if
end tell
if i < 10 then
set NumString to "0000" & i
else if i < 100 then
set NumString to "000" & i
else if i < 1000 then
set NumString to "00" & i
else if i < 10000 then
set NumString to "0" & i
else
set NumString to i
end if
tell application "Terminal" to do script "screencapture -ix " & quoted form of (POSIX path of (ImageFolder as alias) & "ScreenShot" & NumString & ".png")
tell application "Finder" to set visible of process "Terminal" to false
delay 0.1
--do shell script "screencapture -ix " & quoted form of (POSIX path of (ImageFolder as alias) & "ScreenShot" & NumString & ".png")
do shell script "/usr/local/bin/mousetools -x " & (item 1 of Dims) + 1 & " -y " & (item 2 of Dims) + 1
do shell script "/usr/local/bin/mousetools -leftClickNoRelease"
do shell script "/usr/local/bin/mousetools -x " & (item 3 of Dims) - 1 & " -y " & (item 4 of Dims) - 1
do shell script "/usr/local/bin/mousetools -releaseMouse"
tell application runningApplication to activate
delay 0.1
if KeyType is not "click" then
tell application "System Events"
if KeyType is "stroke" then
keystroke PageKey
else if KeyType is "code" then
key code PageKey
end if
end tell
else
--do shell script "/usr/local/bin/mousetools -x " & X & " -y " & Y
--do shell script "/usr/local/bin/mousetools -leftClick"
do shell script "/usr/local/bin/cliclick c:" & X & "," & Y
end if
if SelectedDelay > 0 then delay SelectedDelay
tell application "Terminal" to close every window
end repeat
tell application "ASObjC Runner" to hide progress
tell application "Terminal" to quit
end run
on parseLine(theLine, delimiter)
-- This came from Nigel Garvey
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to {delimiter}
set theTextItems to theLine's text items
set AppleScript's text item delimiters to astid
repeat with i from 1 to (count theTextItems)
if (item i of theTextItems is "") then set item i of theTextItems to missing value
end repeat
return theTextItems's every text
end parseLine
The terminal call proved more reliable than gui scripting the command-shift-4.
The script worked fine where I tested it. I was just hoping to get some independent testing so I can find flaws before normal use. Also just wanted to share it with the community that’s helped me so much.
if i < 10 then
set NumString to "0000" & i
else if i < 100 then
set NumString to "000" & i
else if i < 1000 then
set NumString to "00" & i
else if i < 10000 then
set NumString to "0" & i
else
set NumString to i
end if
You can use the old trick to pad an integer with zeros. You want 5 digits and i is an integer and not an empty string.
If there’s no way to ignore a “do shell script” command then I can’t use it there. The script uses the interactive screen capture, so it has to execute the mousetools commands before the screencapture call is complete. With terminal I can ignore.
I’m playing more with using command-shift-4. I can set the save folder with a “defaults write com.apple.screencapture location /path/to/folder” (which isn’t working actually…), but how can I control the naming scheme? It’s important to see that no pages were skipped in the process somehow.
Yeah, that’s one thing wrong about screen capture. you can’t get a user defined rectangle. Don’t know if this would be faster, but you could get the whole screen and crop the picture somehow.
That was my first attempt. Image events is useless since all cropping is centered on the picture. Seriously no idea why they would do that instead of letting you specify 4 dimensions. My original script used photoshop to crop after full-screen screenshots were taken, but that relies on having photoshop. Here’s the slightly updated version:
(*
This script is for batch screen shots.
Written by Tim Wilson in 2013, but it's not
that great so feel free to take credit.
This script requires that cliclick and mousetools
be installed in /usr/local/bin,
and that the Applescript Objective C Runner
be installed.
cliclick: http://www.bluem.net/en/mac/cliclick/
mousetools: http://www.hamsoftengineering.com/codeSharing/MouseTools/MouseTools.html
ASObjC Runner: http://www.macosxautomation.com/applescript/apps/runner.html
*)
property Dims : ""
property ClickDims : ""
property NumPages : ""
property NextPageKeys : {¬
{KeyName:"Right Arrow", KeyCode:124}, ¬
{KeyName:"Down Arrow", KeyCode:125}, ¬
{KeyName:"Space Bar", KeyCode:49}}
on run
-- Get number of pages in document
repeat
set {NumPages, ResultButton} to {text returned, button returned} of (display dialog "Enter number of pages to be captured:" default answer NumPages with title "Number of Pages")
if ResultButton is not "OK" then return 0
try
set NumPages to NumPages as integer
exit repeat
on error
display dialog "Number of pages must be an integer greater than 0!" with title "Number of Pages" with icon stop
end try
end repeat
-- Get screen dimensions for cropping of images
display dialog "Position mouse at top-left of region to capture, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set TempDims to parseLine(TempDims, return)
set Dims to {}
repeat with i from 1 to 2
set end of Dims to item i of TempDims
end repeat
display dialog "Position mouse at bottom-right of region to capture, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set TempDims to parseLine(TempDims, return)
repeat with i from 1 to 2
set end of Dims to item i of TempDims
end repeat
-- Get key used to change pages
set KeyType to "code"
set KeyList to {}
repeat with aKey in NextPageKeys
set end of KeyList to KeyName of aKey
end repeat
set end of KeyList to "Enter Coordinates at which to left-click..."
set end of KeyList to "Manually enter key code..."
set end of KeyList to "Manually enter key stroke..."
set SelectedKey to choose from list KeyList with prompt "Select key used to switch to next page:" with title "Page Change Key" default items {beginning of KeyList} without empty selection allowed
if SelectedKey is false then
return 0
else if item 1 of SelectedKey is "Manually enter key code..." then
repeat
set PageKey to text returned of (display dialog "Enter key code to use to switch pages:" with title "Page Change Key" default answer "")
try
set PageKey to PageKey as integer
exit repeat
on error
display dialog "Key code must be integer!" with title "Page Change Key" with icon stop
end try
end repeat
else if item 1 of SelectedKey is "Manually enter key stroke..." then
repeat
set PageKey to text returned of (display dialog "Enter keystroke to use to switch pages:" with title "Page Change Key" default answer "") as string
if (count of PageKey) is 1 then
set KeyType to "stroke"
exit repeat
end if
display dialog "Key stroke must be a single character!" with title "Page Change Key" with icon stop
end repeat
else if item 1 of SelectedKey is "Enter Coordinates at which to left-click..." then
display dialog "Position mouse at the point that will be left-clicked to change the page, then press ENTER/RETURN"
set TempDims to do shell script "/usr/local/bin/mousetools -location"
set ClickDims to parseLine(TempDims, return)
set X to (item 1 of ClickDims) as integer
set Y to (item 2 of ClickDims) as integer
set KeyType to "click"
else
repeat with aKey in NextPageKeys
if item 1 of SelectedKey is KeyName of aKey then
set PageKey to KeyCode of aKey
exit repeat
end if
end repeat
end if
-- Get delay to use between page change and screen capture
set DelayList to {}
repeat with i from 0 to 30
set end of DelayList to i / 10
end repeat
set end of DelayList to "Longer..."
set SelectedDelay to choose from list DelayList with prompt "Select delay (seconds) between page change and screen capture:" with title "Screen Capture Delay" default items {beginning of DelayList} without empty selection allowed
if SelectedDelay is false then
return 0
else if item 1 of SelectedDelay is "Longer..." then
repeat
set SelectedDelay to text returned of (display dialog "Enter number of seconds to wait between screen captures:" with title "Screen Capture Delay" default answer "")
try
set SelectedDelay to SelectedDelay as integer
exit repeat
on error
display dialog "Key code must be integer!" with title "Screen Capture Delay" with icon stop
end try
end repeat
end if
-- Get folder in which to store captured images
set ImageFolder to choose folder with prompt "Select folder in which to store images:" default location (path to desktop)
tell application "ASObjC Runner"
reset progress
set properties of progress window to {name:"Batch Cropped Screenshots", button visible:true, button title:"Cancel", max value:(NumPages + 1), message:"!!!MOVE ME SO I'M NOT BLOCKING THE REGION TO CAPTURE!!!", detail:"An extra capture will be performed for redundancy", current value:(NumPages + 1)}
activate
show progress
end tell
try
display dialog "After clicking OK, you will have 10 seconds to ensure that the first page is showing and not blocked by anything except THIS window and that the selected key can change pages!!!"
on error
tell application "ASObjC Runner"
hide progress
end tell
return 0
end try
tell application "ASObjC Runner"
set properties of progress window to {detail:"Starting capture process in 10 seconds..."}
end tell
repeat with i from 1 to 100
tell application "ASObjC Runner"
set properties of progress window to {current value:i * (NumPages + 1) / 100}
if i mod 10 is 0 then
set properties of progress window to {detail:"Starting capture process in " & beginning of my parseLine((10 - (i / 10)) as string, ".") & " seconds..."}
end if
if button was pressed of progress window then
hide progress
return 0
end if
delay 0.1
end tell
end repeat
tell application "System Events"
set runningApplication to name of 1st process whose frontmost is true
end tell
tell application "Terminal" to activate
tell application "Finder" to set visible of process "Terminal" to false
repeat with i from 1 to (NumPages + 1)
tell application "ASObjC Runner"
set properties of progress window to {message:"Taking screenshots...", detail:"Page " & i & " of " & NumPages + 1 & " (" & NumPages & ")", current value:i}
if button was pressed of progress window then
hide progress
return 0
end if
end tell
set NumString to text -5 thru -1 of ("0000" & i as text)
tell application "Terminal" to do script "screencapture -ix " & quoted form of (POSIX path of (ImageFolder as alias) & "ScreenShot" & NumString & ".png")
tell application "Finder" to set visible of process "Terminal" to false
delay 0.1
do shell script "/usr/local/bin/mousetools -x " & (item 1 of Dims) + 1 & " -y " & (item 2 of Dims) + 1
do shell script "/usr/local/bin/mousetools -leftClickNoRelease"
do shell script "/usr/local/bin/mousetools -x " & (item 3 of Dims) - 1 & " -y " & (item 4 of Dims) - 1
do shell script "/usr/local/bin/mousetools -releaseMouse"
tell application runningApplication to activate
delay 0.1
if KeyType is not "click" then
tell application "System Events"
if KeyType is "stroke" then
keystroke PageKey
else if KeyType is "code" then
key code PageKey
end if
end tell
else
--do shell script "/usr/local/bin/mousetools -x " & X & " -y " & Y
--do shell script "/usr/local/bin/mousetools -leftClick"
do shell script "/usr/local/bin/cliclick c:" & X & "," & Y
end if
if SelectedDelay > 0 then delay SelectedDelay
tell application "Terminal" to close every window
end repeat
tell application "ASObjC Runner" to hide progress
tell application "Terminal" to quit
end run
on parseLine(theLine, delimiter)
-- This came from Nigel Garvey
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to {delimiter}
set theTextItems to theLine's text items
set AppleScript's text item delimiters to astid
repeat with i from 1 to (count theTextItems)
if (item i of theTextItems is "") then set item i of theTextItems to missing value
end repeat
return theTextItems's every text
end parseLine
I agree about the Image Events cropping. But, I think Shane Stanley posted an ASOC cropping script not too long ago. I’ll try to find it if nobody else does.
Note that you don’t need to save as Cocoa-AppleScript now in Mavericks. You can add Shane’s cropping script into an ASOC library in ~/Library/Script Libraries/ and you don’t lose very much time by using the script library. That’s very quick also.
Edited: and btw, if you find anything wrong with Shane’s script, don’t ask me. I’m just a spreader of good news.
Yeah. Wish I had more experience with cocoa and applescript libraries in general. The changes with 10.9 seem pretty exciting. Looking forward to see some of the awesome things that Shane and McUsr come up with!
The roofers are done. No leaks in our roof now we hope.
I hope the gurus can tell you how to add that to the library, as I’m working on something and might sleep also. If not, I want to add Shane’s cropping module into the library too, but it will probably take me a lot longer then they would.
Good to hear about the roof! Apparently the phrase “raining cats and dogs” came from when people had thatched roofs and pets would sleep up there to stay warm. Heavy rain and… well you get the picture.
Anyways, glad my roof isn’t thatched, and yours too!
Get some sleep. Thanks for the help, and even more for the useful information!
Oh and one last thing, did you use something like:
do shell script “(osascript -e ‘say "hello"’; osascript -e ‘beep 3’)”
This runs the two processes one after the other. But, it looks like you know this.
here’s a version of the cropping script suitable for an ASObjC library:
use framework "Foundation"
on cropTo:{x1, y1, x2, y2} fromPath:inputPath toPath:outputPath
set newWidth to x2 - x1
set newHeight to y2 - y1
set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:inputPath
set theSize to (theImage's |size|()) as record
set oldHeight to height of theSize
-- transpose y value for Cocoa coordintates
set y1 to oldHeight - newHeight - y1
set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}}
theImage's lockFocus()
set theRep to current application's NSBitmapImageRep's alloc()'s initWithFocusedViewRect:newRect
theImage's unlockFocus()
set theData to theRep's representationUsingType:(current application's NSPNGFileType) |properties|:{NSImageGamma:1.0}
theData's writeToFile:outputPath atomically:true
end cropTo:fromPath:toPath:
I saved Shane’s ASOC script as script bundle. Called it with this:
use cropScript : script "CropFileToFilelib"
use scripting additions
-- choose the file to crop
set f to (choose file)
set pp to POSIX path of f
-- create file specification for new file
set desk_path to POSIX path of (path to desktop)
set fs to desk_path & "NewFile.png"
cropScript's cropTo:{50, 50, 500, 500} fromPath:(pp) toPath:(fs)
I named the script library (for testing purposes) “CropFileToFileLib” and saved it in the user’s library’s “Script Libraries” folder.
Remember to save the library as script bundle. Then open the drawer and make it a library.