Applescript. Save the results of a script in a var - applescript

I am new with AppleScript. I want to store the results of a script.
I first try
tell application "TextEdit" to get the bounds of the window 1
The result in the Script Editor is: {1445, 366, 1920, 1095}
How can I store each number in a separate way to use it later in the script?
What I have tried:
-- set firstNumber to (1st item of the result)
-- set secondNumber to (2nd item of the result)
I think I can store one of the numbers but not all of them. I could declare the first line each time but it seems very inefficient

You can assign the list to 4 different variables in one line
tell application "TextEdit" to set {xPosition, yPosition, width, height} to bounds of window 1

Related

Print result from two functions to console

I've created a small script to get the current size and position of a window. Unfortunately, I only can get one result at a time and need to comment/uncomment the other one.
tell application "System Events" to tell process "Toggl Track"
# get position of window 1
get size of window 1
end tell
If I uncomment both, I will only get the size.
So, my I tried to write the results in a variable and then log these instead.
tell application "System Events" to tell process "Toggl Track"
set position to get position of window 1
set size to get size of window 1
log position & size
end tell
With this, I get no result at all.
What did I do wrong?
position and size are part of the terminology of System Events. You cannot use them as variable names. Rename the variables
tell application "System Events" to tell process "Toggl Track"
set windowPosition to position of window 1
set windowSize to size of window 1
log windowPosition & windowSize
end tell
Regarding the first script, get saves the result of the line in the AppleScript property result and overwrites previous values.

Applescript to make the next tab the current document in Photoshop

I'm nearing the end of finishing an application that uses Google Vision to identify articles of clothing, and automatically crop to the article in Photoshop.
The first script I have goes through all of the open images and grabs their dimensions and file paths. Those are then passed to my application to do the rest of the magic.
I ran into an issue where it just kept cropping the same image over and over again. After a bit of debugging I realized that this is due to the fact that it will only crop the image that is "active" in photoshop.
So, I'm faced with the task of needing to call another script that sets the next tab as the active document in PS before hitting the crop function.
I was hoping for something easy like:
tell application "Adobe Photoshop 2020"
set currentDocs to documents
set currentDoc to next item of currentDocs
end tell
Well, that doesn't work...
tell application "Adobe Photoshop 2020"
set currentDocs to documents
-- set myList to {}
repeat with n from 1 to count of currentDocs
set currentDoc to item (n + 1) of currentDocs
end repeat
end tell
This works, but errors after the last tab because it goes out of bounds. (not sure how to stop that?) And the fact that it's in a loop doesn't help either. I just need a simple script that sets the currentDoc to the NEXT open image. Then I Can crop, move to the next image, crop, etc.
But it also needs to know the last tab so it doesn't go back through the images.
Use the repeat with loopVariable (in list) syntax instead.
Then set current document in each turn of the loop.
For example, the following loops over all open documents and crops them to 100 x 100 pixels:
tell application "Adobe Photoshop 2020"
repeat with doc in documents as list
set current document to doc
crop doc bounds {0, 0, 100, 100}
end repeat
end tell

Close dialog that contains static text

I am trying to write a script that should press OK button on every dialog that contains particular static text:
tell application "System Events"
set theProcesses to application processes
repeat with theProcess from 1 to count theProcesses
tell process theProcess
repeat with x from 1 to count windows
set texts to static text of window x
repeat with t from 1 to (count texts)
if (static text t of window x whose value is "Particular text") then
click first button whose value is "OK" of window x
end if
end repeat
end repeat
end tell
end repeat
end tell
This script is not executing. Something is wrong with the if statement.
You have to check
if (value of static text t of window x is "Particular text")
The try block is necessary to ignore the error if an OK button does not exist.
tell application "System Events"
set theProcesses to application processes
repeat with theProcess from 1 to count theProcesses
tell process theProcess
repeat with x from 1 to count windows
set texts to static text of window x
repeat with t from 1 to (count texts)
if (value of static text t of window x is "Particular text") then
try
click (first button of window x whose value is "OK")
end try
end if
end repeat
end repeat
end tell
end repeat
end tell
As an alternative method of doing this—and one that may, in theory, be quicker if you had a large number of processes, windows and text objects to loop through—would be to grab everything all at once. This could be done in one, single line, but breaking it down into separate lines doesn't sacrifice anything in terms of speed and affords a much easier-to-read code block:
tell application "System Events"
set P to a reference to every process
set W to a reference to every window of P
set S to a reference to (static texts of W whose value is "Particular text")
set C to a reference to value of attribute "AXParent" of S
set B to a reference to (buttons of C whose name is "OK")
repeat with _b in B
click _b
end repeat
end tell
Breaking it down with some crude explanations:
P: contains a reference to every process. Because it's a reference to every process, rather than simply the processes themselves, AppleScript doesn't need to expend any energy determining what these processes are just yet. It basically stores the instruction to do this later on within the variable P.
W: is a reference to every window of every process. So far, these two lines are the equivalent of your first two repeat loops, except no computations have been exercised here so far, whereas your script will have gone through (count windows × count processes computations).
S: is a reference to all the static texts matching your required value. For all intents and purposes, this "bypasses" the need to loop through them individually, and just grabs the right ones immediately (in reality, a loop is still performed, but it's performed at a deeper code level, which takes a fraction of the time than any loop performed by AppleScript).
C: contains the parent of the static text, which is the window containing said text. (C stands for container = parent)
B: is then, ultimately, the collection of all the "OK" buttons of every window that contains the specific static text. It's a reference to them, so, again, can just grab them all in one go, avoiding the repeat loops.
Finally, to click the buttons in B, we do our first real bit of computation, and loop through B. However, B only contains the buttons that need to be clicked, so there are no wasted iterations, and hence no need for a try...end try block.
As each button from B is accessed through variable _b, this is where AppleScript throws all of those references together, and performs the necessary (and only the necessary) computations needed to retrieve the buttons, which can equate to as few as one single iteration of each of your repeat loops.
Though this is already a very efficient means of acquiring the buttons you need to click, we can filter the processes and windows to improve it further:
tell application "System Events"
set P to a reference to (processes whose background only is false)
set W to a reference to (windows of P whose subrole is not "AXUnknown" and role is "AXWindow")
set S to a reference to (static texts of W whose value is "Particular text")
set C to a reference to value of attribute "AXParent" of S
set B to a reference to (buttons of C whose name is "OK")
repeat with _b in B
click _b
end repeat
end tell
As I mentioned, the additional advantage is that this requires much less—if any at all—error handling (systems running OS X versions earlier than MacOS 10.12 may need to wrap the repeat loop here in a try block; in MacOS 10.12 and later, this is not needed). The try block was needed in your script in the event that first button of window x whose value is "OK" returned an empty result, which would cause click to throw an error. Here, B only contains the buttons named "OK" (change name to value if needs be), so click will always have something to click.
However, for the sake of robustness and to cater for earlier systems, it's probably good practice to be thoughtful when it comes to error-handling:
try
repeat with _b in B
click _b
end repeat
end try
So it might be worth mentioning that your script may need additional error-handling than just the single try block: once the button "OK" is clicked, my guess is that the dialog box disappears. When this happens, the all the static text objects it contains disappear along with it, which means that another error will be thrown by the incomplete repeat with t loop.
Thanks to #user3439894 for testing the code in OS X 10.8.6.

How can I programmatically rotate all the pages in a PDF using native macOS tools?

Given a file path, I need to rotate all the pages in a PDF at that file path using nothing that isn't included with macOS. The wrapper for this will be AppleScript, so I also have access to the command line and therefore all of the scripting languages installed by default with macOS, but nothing that would require brew or, for example, Python's pip.
PDF page rotation can be performed using standard Preview application.
Just after opening a PDF document, hit 1 or 2 times tab key (depending of your Preview version) in order to select all pages on side bar with 'cmd a'.
Then hit 'cmd R' (or L) to rotate 90% right (or left) all selected pages and finally hit 'cmd w' to close the window.
Preview will ask to save changes by default, then only hit enter key to save.
You can simulate all these keys in Applescript using Keystroke instruction from System Events.
To use this subroutine for many PDF files, you have 2 solutions, still in Applescript:
1) Select a folder and loop through all PDF files of that folder to apply this sub-routine.
2) Select your files in "Finder" and your script will take directly all Finder selected files to apply same sub-routine.
Such GUI scripting is not perfect, but Preview is not scriptable ...unfortunately )-:
To my knowledge macOS does not have any one native command line Unix executable that can rotate all pages in a PDF (while keeping text based ones text based). sip can rotate a single page PDF however the resulting PDF is an encapsulated image, not text if it was text base to begin with. Also, not sure if there is a way with just plain AppleScript, other then via UI Scripting the default Preview application, without going to AppleScriptObjC (Cocoa-AppleScript) and or Python, etc.
Using third-party command line utilities is probably the easiest, but you said it has to be done only using what's a default part of macOS. So, I'll offer an AppleScript solution that uses UI Scripting the default Preview application, that can be used in the event there isn't another way with AppleScriptObjC or without third-party utilities, etc.
This solution, as offered (and coded), assumes that Preview is the default application for PDF documents and uses it to rotate all the pages in the PDF document. It is also setup as an Automator workflow. (Although there are other ways to incorporate the AppleScript code shown below.)
First, in Finder, make a copy of the target PDF documents and work with those.
In Automator, create a new workflow document, adding the following actions:
Get Specified Finder Items
Add the copied target PDF document to this action.
Run AppleScript Script
Replace the default code with the code below:
AppleScript code:
on run {input}
set thisLong to 0.25 -- # The value of 'thisLong' is decimal seconds delay between keystrokes, adjust as necessary.
set theRotation to "r" -- # Valid values are 'l' or 'r' for Rotate Left or Rotate Right.
set theViewMenuCheckedList to {}
set theMenuItemChecked to missing value
repeat with thisItem in input
tell application "Finder" to open file thisItem -- # By default, in this use case, the PDF file will open in Preview.
delay 1 -- # Adjust as necessary. This is the only 'delay' not defined by the value of 'thisLong'.
tell application "System Events"
perform action "AXRaise" of window 1 of application process "Preview" -- # Just to make sure 'window 1' is front-most.
delay thisLong
-- # Ascertain which of the first six 'View' menu items is checked.
set theViewMenuCheckedList to (value of attribute "AXMenuItemMarkChar" of menu items 1 thru 6 of menu 1 of menu bar item 5 of menu bar 1 of application process "Preview")
repeat with i from 1 to 6
if item i in theViewMenuCheckedList is not missing value then
set theMenuItemChecked to i as integer
exit repeat
end if
end repeat
-- # Process keystrokes based on which 'View' menu item is checked.
-- # This is being done so the subsequent keystroke ⌘A 'Select All'
-- # occurs on the 'Thumbnails', not the body of the document.
if theMenuItemChecked is not 2 then
repeat with thisKey in {"2", "1", "2"}
keystroke thisKey using {option down, command down}
delay thisLong
end repeat
else
repeat with thisKey in {"1", "2"}
keystroke thisKey using {option down, command down}
delay thisLong
end repeat
end if
repeat with thisKey in {"a", theRotation as text, "s"} -- # {Select All, Rotate Direction, Save}
keystroke thisKey using {command down}
delay thisLong
end repeat
keystroke theMenuItemChecked as text using {option down, command down} -- # Resets the 'View' menu to the original view.
delay thisLong
keystroke "w" using {command down} -- # Close Window.
end tell
end repeat
end run
Notes:
As this script uses UI Scripting, when run from Automator (or Script Editor), the running app must be added to System Preferences > Security & Privacy > Accessibility in order to run properly. Saved as an application, the saved application would need to be added.
Also with UI Scripting, the value of the delay commands may need to be changed for use on your system (and or additional delay commands added as appropriate, although in the case additional delay commands should not be needed). It should go without saying however test this on a set of a few document first to make sure the value set for thisLong works on your system. On my system this worked as coded.
When using UI Scripting in this manner, once the task has started, one must leave the system alone and let it finish processing the files. Trying to multi-task will only set focus away from the task at hand and cause it to fail.
If you need to rotate more then one time, add additional theRotation as text, to:
repeat with thisKey in {"a", theRotation as text, "s"} -- # {Select All, Rotate Direction, Save}
Example:
repeat with thisKey in {"a", theRotation as text, theRotation as text, "s"}
You can use a method of the PDFPage from a Cocoa-AppleScript.
Look at https://developer.apple.com/documentation/pdfkit/pdfdocument?language=objc and https://developer.apple.com/documentation/pdfkit/pdfpage?language=objc
Here's the script:
use scripting additions
use framework "Foundation"
set thisPath to POSIX path of (choose file of type {"pdf"} with prompt "Choose a PDF") -- a POSIX path, not an alias or an HFSPath
set thisDoc to current application's PDFDocument's alloc()'s initWithURL:(current application's NSURL's fileURLWithPath:thisPath)
set pCount to thisDoc's pageCount()
repeat with i from 0 to (pCount - 1)
((thisDoc's pageAtIndex:i)'s setRotation:90) -- rotate to 90, the rotation must be a positive or negative multiple of 90: (0, 90, 180, 270 or -90 -180 -270)
end repeat
thisDoc's writeToFile:thisPath -- save --
Note: this script will work properly, if the rotation of the page is 0 (no rotation), otherwise you must get the rotation of the page and do a calculation.
Here's an example to rotate the pages to right or left:
use scripting additions
use framework "Foundation"
set thisPath to POSIX path of (choose file of type {"pdf"} with prompt "Choose a PDF") -- a POSIX path, not an alias or an HFSPath
my rotatePages(thisPath, 90) -- rotate right , use -90 to rotate left
on rotatePages(thisPath, r)
set thisDoc to current application's PDFDocument's alloc()'s initWithURL:(current application's NSURL's fileURLWithPath:thisPath)
set pCount to thisDoc's pageCount()
repeat with i from 0 to (pCount - 1)
set thisPage to (thisDoc's pageAtIndex:i)
(thisPage's setRotation:((thisPage's |rotation|()) + r)) -- add 90 to the current rotation, note: at 360, the value of the rotation will be set to 0, not to 360
end repeat
thisDoc's writeToFile:thisPath -- save --
end rotatePages
You can now use Swift as a scripting language. The Shortcuts.app includes an option to use Swift in its "Run Shell Script" action.
The following script will rotate all pages of PDFs supplied as filenames in its arguments.
#!/usr/bin/swift
// ROTATE PAGES: Rotate all pages of selected PDFs by 90˚
import Foundation
import Quartz
func newURL(filepath: String, newbit: String) -> String {
var newname = filepath
while (FileManager.default.fileExists(atPath: newname)) {
newname = (newname as NSString).deletingPathExtension
// Could be improved with incremental number added to filename
newname += newbit
}
return newname
}
func rotatePage(filepath: String) -> Void {
let pdfURL = URL(fileURLWithPath: filepath)
let newFilepath = newURL(filepath: filepath, newbit: " +90.pdf")
if let pdfDoc = PDFDocument.init(url: pdfURL) {
let pages = pdfDoc.pageCount
for p in (0...pages) {
let page = pdfDoc.page(at: p)
var newRotation: Int = 90
if let existingRotation = page?.rotation {
newRotation = (existingRotation + 90) as Int
}
page?.rotation = newRotation
}
pdfDoc.write(toFile: newFilepath)
}
return
}
// "main"
if CommandLine.argc > 1 {
for (index, args) in CommandLine.arguments.enumerated() {
if index > 0 {
rotatePage(filepath: args)
}
}
}
Here's the solution I used.
Automator to separate the PDF into individual pages in a new folder at the same level as the original PDF
Some AppleScript to rotate a single page PDF using Image Events
A second Automator workflow to stitch the separate PDFs back together again
An AppleScript that controls everything, using do shell script to open each of the Automator workflows as needed.
MacOS comes with python, and you can use it in Automator (Run shell script - set to python, pass inputs as arguments) to create a service that will work on PDFs in the Finder.
#!/usr/bin/python
# coding=utf-8
import sys
import os
from Quartz import PDFDocument
from CoreFoundation import NSURL
if __name__ == '__main__':
for filename in sys.argv[1:]:
filename = filename.decode('utf-8')
shortName = os.path.splitext(filename)[0]
pdfURL = NSURL.fileURLWithPath_(filename)
pdfDoc = PDFDocument.alloc().initWithURL_(pdfURL)
pages = pdfDoc.pageCount()
for p in range(0, pages):
page = pdfDoc.pageAtIndex_(p)
existingRotation = page.rotation()
newRotation = existingRotation + 90
page.setRotation_(newRotation)
pdfDoc.writeToFile_(filename)
You can find a whole load of similar scripts and Automator workflows at this GitHub site

Applescript: Get absolute path of item in a File > Open window

I'm trying to automate JPEGmini which is not entirely scriptable - the only way to control it is via the "Open" dialog.
Repeatedly calling it once per image is extremely slow.
Instead I'm trying to to perform a search for .jpg or .jpeg files in the "Open" dialog, so that within that result set I can select the batch of files I want to process and open them in a single pass.
(More detail after the code example)
-- later within the search results list I get, I want to select these
set filesToSelect to {"/Users/me/Desktop/photo.jpg", "/Users/me/Documents/image.jpeg"}
-- the actual app is JPEGmini but the same principle applies to Preview
tell application "Preview"
-- start the app
activate
-- let it boot up
delay 3
-- ensure it still has focus
activate
end tell
tell application "System Events"
tell process "Preview"
-- spawn "Open" window
keystroke "o" using {command down}
delay 1
-- spawn "Go to" window
keystroke "g" using {command down, shift down}
delay 1
-- Go to root of hard drive
keystroke "/"
keystroke return
delay 1
-- Perform search
keystroke "f" using {command down}
delay 1
keystroke ".jpg OR .jpeg"
keystroke return
end tell
end tell
Having done the above, how do I access the list of search results, repeat over each item and get it's absolute file path?
I've tried a few variations but am getting nowhere.
Thanks for your time.
You could simplify this by using cross between the shell command line and applescript.
set jpegSearch to paragraphs of (do shell script "mdfind -onlyin / 'kMDItemContentType == \"public.jpeg\" '")
The mdfind command
consults the central metadata store and returns a list
of files that match the given metadata query. The query can be a string
or a query expression.
AFAIK this is what spotlight uses.
mdfind documents will give you an idea of how this command works. But ths script searches only in "/" and looks for content type attribute that is public.jpeg
The return is text. A list of POSIX Paths of the matching files. This text is like a text document each path on a new line but in effect one item as far as Applescript is concerned.
They need to be broken up in a applescript list. So I do this by asking for the paragraphs of the result.
Also if you want to open a new finder window:
try something like:
tell application "Finder"
activate
set myWindow to make new Finder window to startup disk
end tell
To answer why you are getting your error in trying to get the POSIX paths of the target of the files.
If you look at the properties of one of the files returned in your Search Window.
tell application "Finder" to set fileList to properties of first file of front window
You will see the file properties have a property named original item
If you really want to do it the way you are doing it then one way is to get the original item. Coerce the result into alias form then get the posix path.
tell application "Finder" to set aFile to POSIX path of (original item of first file of front window as alias)
In a normal finder window you can use.
tell application "Finder" to set fileList to POSIX path of (first file of front window as alias)
These are just examples to show you whats going on.
The difference in the type of finder window results is because in a Search window what is being displayed is alias files (class:alias file) to the original files, hence the original item property.
Update 2.
To go through your items in your list and check them against another list is simple.
Apple have some tools that will help you with the code.
When in your script.
crtl + Mouse Click the Variable that will hold the jpg result as a list.
This will give you a contextual menu that contains helper code.
Go to the Repeat routines folder in the menu.
Then to its 'Process Every Item'
This will add a repeat routine to your code.
And from there you can use it to check each item against your other list.
set yourOtherList to {"/Library/Desktop Pictures/Abstract/Abstract 2.jpg", "/Library/Desktop Pictures/Abstract/Abstract 1.jpg"}
set jpegSearch to paragraphs of (do shell script "mdfind -onlyin / 'kMDItemContentType == \"public.jpeg\" '")
repeat with i from 1 to number of items in jpegSearch
set this_item to item i of jpegSearch
if this_item is in yourOtherList then
-- do something
log this_item
end if
end repeat
The repeat routine works like this.
It loops over each item in the given list
It will iterate from item 1 to the count of the items in the list.
The i variable holds the loop iteration number. I.e it will be 1 on the first loop and 300 on the 300th loop.
so on the first loop set this_item to item i of jpegSearch is equivalent to writing set this_item to item 1 of jpegSearch
But apple saves you having to write each number of each item with the Repeat with i..
The variable i can be any word or letter you choose that conforms to the normal allowed Variable naming syntax.
Something you can do is build a new list from the matched items. by copying them into a previously declared list. You can then work on that list after the repeat loop has completed.
Here bigList is declared as a empty list {}
The matched items are copied to the bigList. Each new item it receives is added to the end of the list.
set bigList to {}
set yourOtherList to {"/Library/Desktop Pictures/Abstract/Abstract 2.jpg", "/Library/Desktop Pictures/Abstract/Abstract 1.jpg"}
set jpegSearch to paragraphs of (do shell script "mdfind -onlyin / 'kMDItemContentType == \"public.jpeg\" '")
repeat with i from 1 to number of items in jpegSearch
set this_item to item i of jpegSearch
if this_item is in yourOtherList then
copy this_item to end of bigList
end if
end repeat
bigList

Resources