Batch screen shot utility

Hey All,

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

Hi scriptim,

For one, you don’t need to launch Terminal to take screenshots.

What do you think is lacking in the script? For instance, does it take to long at some points?

gl,
kel

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.

Thanks,

Tim

Here’s another one. In this section:

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.

set NumString to text -5 thru -1 of ("0000" & i)

I like that one. Thanks!

Hi scriptim,

But, you can use ‘do shell script “screencapture …”’ instead of activating Terminal using ‘do script’.

gl,
kel

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.

gl,
kel

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.

gl,
kel

Found the link:
http://macscripter.net/viewtopic.php?id=41446

Excellent. Thanks for the link kel!

Hi scriptim,

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. :smiley:

gl,
kel

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!

Hi scriptim,

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.

gl,
kel

Thanks Kel,

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!

Cheers,

Tim

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.

Have a good day,
kel

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.

It’s working great Shane. Thanks!

gl,
kel