AppleScript + Core Image

Cocoaフレームワークを簡単に AppleScript で利用できるようになった...のは確かなんですが、実際のコードがなきゃもう一つ理解が進みませんし、利用もされません。

AppleScript に新機能が追加されるのはいいのですが、いつもこのあたりの情報が少なすぎていまいち浸透しない。

ってなわけで色々と公開してみます。実際の利用例ってやつを、ね。

簡単に効果が分かるってことで Core Image を使ってみます。Mac OS X 10.4 の頃に鳴り物入りで導入されましたね。GPU を使った高速な画像処理がウリです。AppleScript で Core Image を使うことで Image Events 以上の画像処理が可能になります。

Core Image の分かりやすい解説は Apple のものだったりします。英語なんですがね。

では、使ってみましょう。

iOS 7 に新しく追加された カメラフィルタがあります。これらは Core Image のフィルタとして提供されています。

iPhone カメラフィルタ一覧

もちろん、これらのフィルタは iOS だけではなく、OS X でも利用できます。手順としては次のようになります。

  1. 画像ファイルを開く
  2. フィルタを生成
  3. 画像にフィルタを適用
  4. 適用された画像を保存

それぞれ見ていきます。

画像ファイルを開く

扱う画像ファイルは JPEG のみとします。

とりあえず、新規スクリプトファイルを作ります。作成したら以下のように記述し、スクリプトバンドル形式で ~/Library/Script Libraries に CoreImage.scptd という名前で保存します。

Script Editor で開く

use framework "QuartzCore"

property CIImage : class "CIImage"

property NSString : class "NSString"
property NSURL : class "NSURL"

保存します。

スクリプト保存ダイアログ

保存したら「バンドルの内容」を表示し、「AppleScript/Objective-C ライブラリ」にチェックを入れます。

ライブラリとして利用するため「AppleScript/Objective-C ライブラリ」をチェック

再度、保存します。これでライブラリとして活用できるようになりました。では、説明。

まず、Core Image を使うので Core Image を含むフレームワークの QuartzCore.framework を取り込んでいます。Objective-C では

import <QuartzCore/CoreImage.h>

で CoreImage だけを取り込めますが、どうもそういうことができないようで QuartzCore.framework 全てを取り込んでいます。

property として CIImage、NSString、NSURL を定義していますが、これは単純に

  • タイプを短くするため
  • スクリプトを少しでも見やすくするため

です。別名を付けてもいいのですが、分かりやすい方がいいでしょう。

では、AppleScript から渡した画像ファイルを CIImage として開きます。

Script Editor で開く

on openImageFile(imageFile)
    -- imageFile: POSIX path 形式のファイルパス

    -- AppleScript text を NSString に変換
    set imageFile to NSString's stringWithString:(imageFile)

    -- ファイルパスを NSURL(file://)に変換
    set fileURL to NSURL's fileURLWithPath:imageFile

    -- CIImage を生成
    return CIImage's alloc()'s initWithContentsOfURL:fileURL
end openImageFile

なんでいちいち NSString に変換しているのか?

変換しなくてもいきなり NSURL に AppleScript の text クラスを渡しても大丈夫なようなのですが、念のためです。基本的には AppleScriptObjC には Cocoa - AppleScript での型の自動変換といった機能はありません。そのため両者では明示的に型を変換しておいた方が変なエラーで悩まなくてすみます。

CIImage を生成するメソッドはいくつかありますが、ここでは initWithContentsOfURL: を使っています。ここで気になるのは『init で作ったオブジェクトの解放は誰が担うんだ』ということですが、多分、気にしなくてもいいと思います。

ちょっと調べきれていないのですが、ASOC は ARC を採用しています。いつの間にか GC じゃなくなっていました。確か、登場当初は GC だったと思うのですが、ARC が導入されたあたりで ASOC も ARC になったんだと思います。

なので、参照されなくなった時点で解放されると思います。ほんまかいな?

AppleScriptObjC でのメモリ管理に関する情報が少なすぎてなんともいえないのですが、少なくともやってはいけないことは分かっています。それは、明示的にオブジェクトを release することです。一発で落ちるということはありませんが、そのうち AppleScript Editor が落ちます。

ARC については以下のサイトを。

ARC が採用されているからといって ARC が AppleScript で利用できるというわけではありません。あくまで AppleScript から利用している Cocoa のオブジェクトが ARC で管理されているということです。

AppleScriptObjC のメモリ管理に関しては以下を。

これぐらいしか情報がない...。OS X Mavericks 以前は、また異なるんですけどね、状況が...。

画像ファイルから CIImage が生成できたので次は ファイルタを作成します。

フィルタを生成

Core Image ではフィルタの入力に画像(または動画)を渡し、出力でフィルタが適用された画像を取り出します。基本的には、

  1. フィルタに画像を渡す
  2. フィルタの設定値を変更する
  3. フィルタから画像を取り出す
  4. 取り出した画像を次の画像に渡す(1 に戻る)...

これらの繰り返しで処理が行われます。

フィルタは CIFilter を利用します。ライブラリスクリプトの先頭の property に CIFilter を追加します。

(* 前略 *)
....
property CIImage : class "CIImage"
property CIFilter : class "CIFilter"
...
(* 後略 *)

CIFilter は「フィルタの名前を指定して生成」と「フィルタの名前と入力値を同時に指定して生成」する 2 種類の方法があります。後者はスクリプトが読みにくくなるので前者を採用します。

Script Editor で開く

on filterWithName(filterName)
    -- CIFilter をフィルタの名前で生成
    set filter to CIFilter's filterWithName:filterName
    -- フィルタの設定を初期値に
    filter's setDefaults()

    return filter
end filterWithName

このハンドラをライブラリスクリプトに追加します。

フィルタを生成した後、フィルタの設定値をデフォルトにしています。iOS では必要ないのですが、OS X では必須です。

フィルタにどんな種類があるかは以下を参照。

iOS より OS X の方が利用できるフィルタは多いです。これでフィルタも生成できるようになったので画像に適用してみましょう。

画像にフィルタを適用

iOS 7 のカメラで利用できるフィルタは以下の通りです。

  • モノ(CIPhotoEffectMono)
  • 色調(CIPhotoEffectTonal)
  • ノアール(CIPhotoEffectNoir)
  • フェード(CIPhotoEffectFade)
  • クローム(CIPhotoEffectChrome)
  • プロセス(CIPhotoEffectProcess)
  • トランスファー(CIPhotoEffectTransfer)
  • インスタント(CIPhotoEffectInstant)

括弧内は CIFiltet を生成するときに必要なフィルタの名前です。

ライブラリスクリプトにフィルタに画像を渡し、フィルタを適用し、画像を出力するハンドラを追加します。

Script Editor で開く

on applyEffect(imageFile, photoFilterName)
    -- CIImage を生成
    set imageRef to my openImageFile(imageFile)

    -- CIFiter を生成
    set filter to my filterWithName(photoFilterName)

    -- フィルタの設定値を入力
    filter's setValue:imageRef forKey:"inputImage"

    -- フィルタが適用された画像を取得
    return filter's valueForKey:"outputImage"
end applyEffect

CIFilter には色々な入力値があります。これらの値を変更することで画像に色々な処理を施すことができます。今回利用するカメラ用のフィルタには画像以外の入力値がありません。

フィルタが適用された画像は CIFilter の outputImage メソッドで取り出すことができます。

最後にこの画像をファイルに保存します。

適用された画像を保存

スクリプトライブラリでは画面への描画が行えません。Xcode で ASOC アプリケーションを作れば話は別ですが、ウィンドウやビューがない AppleScript Editor で作る ASOC アプリケーションや AppleScript のライブラリでは直接ディスプレイ上に画像を描画できません(方法はないわけではないですが、それなら Xcode で ASOC アプリケーションを作った方が手っ取り早い)。

NSBitmapImageRep は CIImage から生成するメソッドがあるのでこれを利用し、ファイルに保存します。

Script Editor で開く

on saveAsJPEG(imageRef, theFile)
    -- NSBitmapImageRep を CIImage から生成
    set rep to NSBitmapImageRep's alloc()'s initWithCIImage:imageRef

    -- NSBitmapImageRep から JPEG データを取得
    set jpegData to rep's representationUsingType:(current application's NSJPEGFileType) |properties|:(missing value)

    -- ファイルに保存
    jpegData's writeToFile:theFile atomically:true
end saveAsJPEG

このハンドラをライブラリスクリプトに追加します。また property に NSBitmapImageRep も追加します。

(* 前略 *)
....
property NSString : class "NSString"
property NSURL : class "NSURL"
property NSBitmapImageRep : class "NSBitmapImageRep"
...
(* 後略 *)

JPEG 決めうちですが、NSBitmapImageRep は他の画像形式も扱えます。また、このハンドラではもとの画像が持っている情報(Exif や位置情報)などは失われます。元の画像に上書きするのではないので問題はないと思いますが。

これで必要なハンドラが揃いました。最後にこれを利用するスクリプトを作ります。

クライアントスクリプト

Script Editor で開く

on run
    set inputFile to choose file of type {"public.jpeg"} with prompt "フィルタを適用する画像を選んでください"
    set outputFile to choose file name with prompt "保存先:" default name "Output.jpg"

    tell script "CoreImage"
        set imageRef to applyEffect(POSIX path of inputFile, "CIPhotoEffectFade")
        saveAsJPEG(imageRef, POSIX path of outputFile)
    end tell
end run

選択した画像にフェードフィルタを適用しています。実行すると以下のようになります。

フィルタ適用前/適用後

ちょっと画像が小さくて分かりにくいですが...。

スクリプトを書くのが嫌だというナマケモノには、これらの機能を簡単に試せるようなスクリプトをご用意しました。

バンドル形式になっています。バンドルの中にライブラリを入れています。スクリプトメニューに登録、もしくはそのまま開いて AppleScript Editor で実行してみてください。

AppleScript の新機能 (7) - まとめ

Mavericks というか、AppleScript 2.3 の主な変更点をまとめてきました。

これら以外にも変更点はあります。それらについて、補足...のようなまとめ書き。

全く触れなかった変更点として、

  • iCloud のサポート
  • 通知センターが利用できるように
  • Speakable-Workflow

があります。

iCloud のサポート

これは、AppleScript の書類を複数の Mac 間で共有できるってことなんですが、まだ、使ったことがない...。使ったことがないので、不具合にも遭遇していない。あると便利かなと思うのですが、AppleScript Editor 起動時のファイルオープンダイアログの表示はやめてほしいな...。

通知センター

スクリプト内から処理の経過などを通知センターで表示できるようになりました。というか、そういう命令が新しく追加されました。世の中ではこれが一番話題になっているようですね。通知センターを切ってしまっている身にはそれほど関係がない...。

スクリプトのデバック時に役に立つかな。

使い方は専用の命令を呼び出すだけ。

Script Editor で開く

display notification "通知したい本文"

オプションがいくつかあってタイトルとサブタイトル、通知時の音を指定することができます。

Script Editor で開く

display notification "通知したい本文" with title "タイトル" subtitle "サブタイトル" sound name "Glass"

ここで指定する通知音は、システム環境設定の「サウンド」に表示される音声ファイルの名前です。

System_Prefs_Sounds.png

言い換えるなら /System/Library/Sounds 以下のファイル名です。もちろん、ユーザーが追加したものも利用できます。display notification で表示される通知の設定はシステム環境設定の「通知」で変更できます。

System_Prefs_Notifications.png

ここでは AppleScript Editor が表示されていますが、display notification を利用する(した)アプレットやドロプレットはここの「通知センターで表示する項目」に追加されるので、個別に通知に関する設定が行えます。

利用するときの注意点としては、通知をスクリプトの最後に出すときは『通知を出す前にスクリプトが終了してしまうので、スクリプトの終了前にウェイトをおいてください』ってことです。具体的には、

display notification "通知です"
delay 1 -- 通知を出す前にスクリプトが終了してしまうので、1 秒停止

このような感じです。アプレットやドロップレットとしてスクリプトを保存して利用するときに必要な処理です。通知を OS に登録する以前にアプレットが終了してしまうんですね。ちょっとかっこわるいぞ。

また通知で表示されるウィンドウ(?)はクリックすることで通知を出したアプリケーションを全面に表示させることができます。

これを利用し、

  1. なんらかの処理
  2. 通知を表示
  3. 通知をクリック
  4. 再度、処理を実行

と、連続してスクリプトを実行させることができます。同じ処理を何度も繰り返すスクリプトってどんな状況か思い浮かばないのですが、通知センターから起動できるってのはちょっと便利かも。

Speakable-Workflow

これは Automator の話なのですが、Automator で作ったワークフローで Speakable Item を作ることができるようです。いったいなんのことか分からない人の方が多いような気がしますが。自分で作ったスクリプトやワークフローを音声認識を使って起動させることができます。話しかけて Mac を操作できる...と。ただし、英語に限る。

もともと、Mac には音声認識で操作するための機能は入っていました。システム環境設定の「アクセシビリティ」にある「音声認識コマンド」がそれです。

System_Prefs_Accessibility_Speech_Recognition.png

「入」にすると小さいウィンドウが表示されます。

Speech_ Recognition.png

「esc」キーを押しながら話しかけることで処理が行われます。例えば、「Switch to iTunes」などと話しかけると iTunes が前面に表示されます。

話しかけるといってもなんでもいいわけではなく、内蔵されている「コマンド」を発話しなければいけません。このコマンドは「音声コマンドウィンドウ」を表示させることで調べることができます。

Show_Speech_Commands_Window.png

標準の状態でいくつかのコマンドが入っているわけですが、ここにはない処理を行いたいとき『Automator のワークフローでも作ることができますよ』というのが、今回の新機能になります(AppleScript のスクリプトでも可能。これはかなり昔からできるようになっていました)。

コマンドは ~/Library/Speech/Speakable Items に保管されています(音声認識コマンドを利用したことがなければ、このフォルダはありません)。コマンド自体はプロパティリスト形式のテキストファイルで作成することができます(いつのまにかそうなっていたんですね)。

Automator や AppleScript で作ったコマンドもここに保管しておけば音声認識コマンドで利用することができます。ファイル名は英語で...例えば、「Go to Home Folder.scpt」などと保存しておきます。このファイル名がコマンド名になりますので、話しかけるような英語にしておくと音声認識しやすくていいです。単語一つだけだとなかなか認識されにくかったりしますし、他の似たようなコマンドと間違われてしまいます。

使いこなせれば有用で便利には違いないのですが、

  • 英語しか認識できない
  • その英語もネイティブじゃないと認識されにくい
  • その姿は冷静に考えるとけっこう滑稽

といった欠点があるため、あまり陽の目をみない機能になっています。せっかく専用のキャラクターもいたのにね。

OS_9_Speech_Recognition.jpg

その他

あえて、触れなかった部分もいくつかあります。例えば、ライブラリの用語説明の作り方。これは時間がかかるのでまたいつか、暇があれば...。

Script Library のインストール場所は 4 カ所ありますが、これも触れていませんでした。

  1. ~/Library/Script Libraries
  2. /Library/Script Libraries
  3. アプレット内の Resouces/Script Libraries
  4. アプリケーション内の Resouces/Script Libraries

アプレットやアプリケーション内におさめておけば、別にインストールしてもらう必要がないので便利かもしれません。また、これらの中に入れておくとライブラリはそのアプレット(アプリケーション)実行時のみライブラリが有効になります(言い換えると、他のスクリプトなどからは利用できない)。

コード署名についてですが、触れなかったことの中に『コード署名されたアプレット(アプリケーション)の実行時にはインターネットの接続が必要』というものがあります。これは、アプレット(アプリケーション)が有効なものかどうかを Apple に問い合わせるためです。なんで、触れなかったかというと検証していないからです。

接続されていないと起動時にエラーになりますので、頭の片隅にでもおいときましょう。

今回のバージョンアップにより、AppleScript Language Guide もアップデートされています。主に Script Libraries 関連情報の追加です。

script クラスのリファレンスを見てもらうと分かるように、id、name、version 属性が追加されています。

AppleScript Langage Guide と合わせて AppleScript Release Notes: 10.9 も公開されています。

こんなところかな?

まぁ、色々な不具合については触れてませんがね。

AppleScript の新機能 (6) - GUI Scripting

もうそろそろ新機能の話題は飽きてきた...。これで最後にしたいものです。

AppleScript のいざという時の奥の手。GUI Scripting。ここにもセキュリティの魔の手がのびてきました...。

いや。悪いことではないんですけどね。

GUI Scripting の使用/不使用は、従来はシステム環境設定の「アクセシビリティ」で一括して管理されていました。

これが OS X Mavericks では「セキュリティとプライバシー」の「プライバシー」タブにある「アクセシビリティ」で管理されるようになりました。

このため Mavericks 以前では使用を許可すると全てのアプリケーションでの使用が可能でしたが、Mavericks 以後ではアプリケーションごとに個別に使用を許可するように変更されました。

...困ったものです。以前まではアプレットの起動時に GUI Scripting が許可されているかどうかを調べ、許可されていなかったらアプレットから許可を行うことができたのですが、今後はこの方法が使えないようになったのです。

具体的には次のような処理はできません。

Script Editor で開く

(* スクリプト初期化処理... *)

tell application "System Events"
    set bool to UI elements enabled
    if not bool then
        -- GUI Scripting が利用できないなら
        set UI elements enabled to true
    end if
end tell

(* 以降の処理 *)

System Events の System Events Suite の application クラスにあった UI Elements Enabled 属性は、Process Suite の application クラスの属性に変更されています。かといって、各アプリケーション毎の状況を調べられるわけではなく、読み出しのみの属性になっています。

現状、起動時に UI Elements enabled を設定するようなアプレットやアプリケーションはその処理の時点で、処理が終了します(UI Elements enabled 属性が読み出しのみになっているのでエラーになる)。

今後は、アプレットの起動時などにシステム環境設定を開いて、アプレットやアプリケーションごとに『手動』でアクセシビリティに追加してもらう必要があります。

AppleScript にかぎったことではなく、アクセシビリティにアクセスするようなアプリケーションは(許可しない限り)軒並み同じ目に遭っているので仕方はないのですが...。

また、アプリケーション単位での許可ですので、例えば、AppleScript Editor で作っていたスクリプトをスクリプトメニューから実行させると、実行できなかったりします。AppleScript Editor だけをアクセスビリティに追加してもだめなのです。

スクリプトを利用するアプリケーション、ここでは System UI Server も追加する必要があります。もし、Terminal から osascript でスクリプトを利用するなら、Terminal も追加する必要があります。これは、Automator で作ったアプリケーションでも同様です(もちろん、アクセシビリティに関連する操作をするものだけですが)。

で、問題は AppleScript で作られたアプレットやドロップレットです。これらは毎回利用の許可を求められることがあります。なぜかというと、ここに詳しいです(やっと、日本語訳されました)。

簡単にいうと『property の値が毎回変わるため、同一のアプリケーションとして認識されない』ためです。ここにはこの問題の解決法が書かれているのですが...、まぁ、書かれているようにセキュリティの脆弱性を招くため『自分で利用するもの』だけに適用するのがいいでしょうし、でも、なるべく使わない方がいいかもしれません。

最後に。アクセシビリティに関する Terminal コマンドに tccutil というものがあります。

これはアクセシビリティにアクセスするアプリケーションの一覧をリセットすることができます(要管理者権限)。例えば、以下のようにすると『連絡先』にアクセスするアプリケーションをリセットすることができます。

$ tccutil reset AddressBook

アクセスビリティなら、

$ tccutil reset Accessibility

となります。

現状ではリセットのみ利用できます。その他の操作はできないのですが、アップデートで機能が追加されたりするのかな?