Swift Scripting (Part 4) – The Exciting Conclusion

Previously, we’ve covered the groundwork necessary to use Swift to control scriptable applications. (See parts one, two, and three.) Now we get to the payoff. We’ll have a look at a sample script that leverages the scripting interfaces of the Finder and the Image Events helper application.

Preliminaries

To run the sample script on your own system, you’ll need to build and install the ScriptingUtilities, FinderScripting, and ImageEventsScripting frameworks. If you don’t want to create all of these from scratch, you can find ready-to-build Xcode projects in the SwiftScripting project on GitHub. Whichever path you choose to build the frameworks, be sure to install them in /Library/Frameworks.

Shrinking Images

The sample script operates on the current selection in the Finder. The result of running the script will be the creation of half-scale versions of any images that were selected. Any non-image in the Finder selection will be ignored. The “test” for images will solely rely on file extension, so we’re not talking about a fool-proof form of input verification.

Here’s the script

#!/usr/bin/swift -F /Library/Frameworks

// Image Events sample script.
// Operates on selected images in the Finder
// Outputs half-scale images in same folder as originals

import Foundation
import FinderScripting
import ImageEventsScripting
import ScriptingUtilities

let imageExtensions = ["png", "jpg", "jpeg", "tif", "tiff", "gif"]

let finder = application(name: "Finder") as! FinderApplication
let imageEvents = application(name: "Image Events") as! ImageEventsApplication

for item in (finder.selection!.get() as! NSArray) {
    if let file = item as? FinderFile {
        let fileExtension = file.nameExtension!.lowercaseString
        if imageExtensions.contains(fileExtension) {
            let fileURL = NSURL(string: file.URL!)
            let outputDirectory = fileURL!.URLByDeletingLastPathComponent!
            let outputFilename = "\((file.name! as NSString).stringByDeletingPathExtension) (half-scale).\(fileExtension)"
            let outputURL = outputDirectory.URLByAppendingPathComponent(outputFilename)
            
            let image = imageEvents.open!(fileURL!) as! ImageEventsImage
            image.scaleByFactor!(0.5, toSize: 0)
            
            image.closeSaving!(.Yes, savingIn: outputURL.path!)
        }
    }
}

The code is straight forward. On lines 14 and 15, the application function supplied by the ScriptingUtilities framework is used for convenience when assigning the finder and imageEvents constants. The work of the script occurs inside the for loop and we’re able to take full advantage of Foundation classes along side the scripting API of the Finder and Image Events. Go ahead and enter the above script into a text file and give it the name Scale Image by Half.swift. Remember to make the script executable using the chmod

chmod +x 'Scale Image by Half.swift'

Script Menu

While you could run the Scale Image by Half.swift script from anywhere on the system, it would be awkward to select some images in the Finder and then bring up a Terminal window to invoke the script. A better solution is to use the system-wide Script Menu to host your bits of automation. You enable the Script Menu via the Preferences of the Script Editor application.

Script Editor Preferences

With the Script Menu enabled, you now have a place to put all of your scripts and keep them organized by application. I like to configure the Script Menu such that the scripts for the frontmost application are displayed at the top of the menu. The screenshot below shows the Script Menu as it appears on my system with the Finder frontmost.

Script Menu with Finder Frontmost

If you select the Open Finder Scripts Folder option from the Script Menu, a Finder window will be opened at the appropriate location. This is where you want to put the Scale Image by Half.swift script. Once the script is placed in this location, it will be available as a menu option when the Finder is frontmost. To exercise the script, select one or more images in the Finder and then choose the Scale Image by Half.swift option from the Script Menu. If all goes well, you should see half-scale images appear alongside the originals.

Happy Scripting!

Advertisements

10 thoughts on “Swift Scripting (Part 4) – The Exciting Conclusion

  1. hello. I meet a problem, please help!
    I use this line “let iTunes = SBApplication(bundleIdentifier: “com.apple.iTunes”) as! iTunesApplication” in you issue.

    but when I want to query the iTunes object. like iTunes.currentTrack.name it always empty string like this “”, also artist. album .etc

    and I want to do like this page One Response to Swift Scripting (Part 4) use application(name: “iTunes”) and Xcode says “Missing argument for parameter ‘delegateHandlesKey’ in call”
    and I copy your ScriptingUtilities in my project. I couldn’t import appUtilities.

    could you tell me which way is the right way to use load the application of iTunes

    Thanks for your time.

    1. Stan,

      Thank you for your question. The ScriptingUtilies framework is intended to be built and installed into /Library/Frameworks. Once you’ve installed ScriptingUtilities, along with your iTunes framework, then you can have a script such as the following:

      #!/usr/bin/xcrun swift -F /Library/Frameworks

      import ScriptingUtilities
      import iTunesScripting

      let iTunes = application(name: "iTunes") as! iTunesApplication

      print(iTunes.currentTrack!.name)

      You should be able to run the above script and it will print out the name of the track currently playing in iTunes. If no track is playing, you’ll get an Optional empty string as the result.

  2. Hi, Tony, Thanks for your reply 🙂

    Now I have another question need to be answer, please :). I successfully build iTunes.swift and iTunesScripting.swift.

    I look into iTunes.h, it is inherit from SBApplication, and it has a property call

    ‘ var running: Bool { get } ‘ to check if that application is running. But it cant be called when I

    use iTunes.swift. Because it inherit from SBApplicationProtocol and NSObjectProtocol, it

    doesn’t hava a property call running. If I want to check the application is running, how could I

    do?

  3. Stan,

    If you change your SBApplicationProtocol to include the following property declaration:


    var running: Bool { @objc(isRunning) get }

    then you’ll be able to access the running property. I’ve just updated the SwiftScripting project on GitHub to include this declaration in the sample frameworks. The sbhc.py script has also been updated to generate this additional declaration in generated Swift files.

    See https://github.com/tingraldi/SwiftScripting/blob/master/Frameworks/iTunesScripting/iTunesScripting/iTunes.swift#L11

  4. if I do sbsc.py to convert Spotify.sdef to SpotifyScripting.swift it will convert twice Application = “applicaiton”, I can just delete one of that and it works. Just let you know. 🙂

    public enum SpotifyScripting: String {

    case Application = “application”
    case Track = “track”
    case Application = “application”
    }

  5. Sorry for the bother 🙂
    Wanna ask a question maybe you know how to fix it.

    Because maxOS application needs to turn on sandbox option of capabilities of Project Target.
    To get access of itunes so that I can get the playerState, currentTrack of iTunes after I turn on sandbox.
    com.apple.iTunes

    com.apple.iTunes.library.read
    com.apple.iTunes.playerInfo
    com.apple.iTunes.playback

    otherwise, playerState always nil.

    OK I fix iTunes access, But I use Spotify in the same way. Transfer to .swift, add
    com.spotify.client

    com.spotify.client.playback
    com.spotify.client.PlaybackStateChanged

    into my entitlement. But strange things happened. It also addObserver and detect spotify playing, paused, stop by notification.userInfo. But not by spotify.playerState, and either every properties in spotify.currentTrack is “”.

    Maybe You have some solution for this. Now I use userInfo to do it alternatively but I dont think it is a good solution. Thanks very much.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s