Archive for 9月, 2014

2014/09/30 vim上でAppleScriptは書けないのか?

Twitter上での「こまったちゃんのつぶやき」(本人に悪意はないが、明らかに内容が間違っている)を拾っていると、新鮮な驚きに遭遇することがあります。

vim(unix上のテキストエディタ。元になったviはSunのビル・ジョイが作った)上でAppleScriptを書けないからどーのという内容でした。

では、実際にvim上でAppleScriptを書いてみましょう。

484d53c0-ad3a-4250-940c-fb2d176a1d6b.jpg

はい。ふつーに書けました。

f4ed6a94-228b-4244-b5fd-18dd6c8744ce.jpg

書いたテキストファイル「test.applescript」の内容をTerminal上でcatしてみて確認し、osascriptコマンドで実行。あらかじめFinder上で選択しておいたファイルの数を音声でかぞえます。

問題なく実行できました(10.9までは、osascript経由のAppleScript実行時には、display dialogとかchoose folderとかのユーザー操作を伴う命令は実行を拒否する仕様になっています。このため、display dialogを使っていません)。

ただ、AppleScript用語辞書の内容をTerminal上から参照できるわけではありませんし、アプリケーションの用語辞書の内容も参照できません。

ase1.jpg

さらに、オートインデントや省略表記を自動展開する機能(end tellやend ifなどを「end」だけ書いておけば構文確認(画面上に「コンパイル」と書いてあるが、一般的なコンパイルではなく実際には中間言語への翻訳。AppleScriptはインタプリタ型言語)時に自動展開してくれる、appと書いておくとapplicationと展開してくれる)も使えません。

構文要素ごとに色分け表示してくれる機能がないと、そのままではとても可読性がよくないです。

AppleScriptエディタ上で利用可能な、エディタ自体と他のツールを連携させたスクリプトアシスタントなど、超便利機能が一切使えないので、生産性はガタ落ちです。

できないわけではないけれど、やると不便。そういうことです。

2014/09/29 record型で結果が返ってくる内容の取り出し方【超基礎】

【ご注意】本エントリは、分かっていない方向けの説明用です。ふつーに分かっている方々にはあらためて参考になるような情報は含まれていません。

Twitter上で「OS X 10.9にしたらAppleScriptの命令の実行結果の返り順が変わった。バグだ」という投稿を見かけました。

よくよく聞いてみると、それはdisplay dialogコマンド(default answer指定時の、文字入力ダイアログ)の結果を(record形式データ)てきとーに取り出して「順番が違う、バグだ」と言っているのでした。

AppleがOSに添付しているAppleScriptをそのまま使ったようで……

apple_sample.png
▲こういう記述は恥ずかしいです、的な記述のオンパレード。読みにくいうえに恥ずかしい。部分的に書いてはダメな記述もある(recordをlistにcastすんな!)。メーカーがOSに添付しているScriptにこれはないだろー(ーー;

そうした(OSについてくる)コードの品質はいまひとつなのと、どーせ英語環境でしかチェックしていないのと、Apple弁はなまりが強い(theとかresultとかやたらと使いまくる)ので、あれを読んで理解しろとかいう方が無茶な感じではあります。

# Adobeのアプリケーションのように、同じオブジェクトの属性値を求めて、返り値内のラベルの順番が変わるとかいうのは、かなり問題ですが、この(↑)程度なら問題はないレベルです。

そこで、実際に解説を。

dsp.png

ダイアログを表示して文字入力を行うのは、だいたいこんな感じです。ダイアログのタイトルとか、ダイアログに対するアイコン表示指定なんかもできますが、それは省略しています。

スクリプト名:dialog0
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

ここ(↑)に掲載しているScriptは、WebブラウザからAppleScriptエディタ(10.10からはスクリプトエディタに改称)に直接コード内容を転送するURLリンクを含んでいます(そのまま実行する機能はないのでセキュリティホールにはならないはず)。

MacのWebブラウザで「▼新規書類に」をクリックすると、AppleScriptエディタが立ち上がって(セキュリティ設定次第では、確認ダイアログが出る場合も)新規Scriptが作成されてそこに内容が転送されます(iOSやWindowsなど他のOSではURLリンクは無効です)。

ここで返ってくる値は、

スクリプト名:dialog1
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”
–> {button returned:”OK”, text returned:”あああああ”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

のようになります。{button returned:”OK”, text returned:”ああああ”}といった内容です。

この内容が何なのかを調べると……

スクリプト名:dialog2
set a to display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”
class of a
–> record

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

recordと返ってきます。recordはラベルのついた値の組み合せで構成されるデータで、ここでは「button returned」と「text returned」の2つのラベルがついたデータのセット(record)が返ってきていることが分かります。

recordから値を取り出すには、ラベルを指定すればOKです。また、record型のデータ内では、ラベルの順番などはとくに意識しないで処理できます。

で、正しくデータを取り出すにはそれぞれラベルを指定してあげる必要があるので、このような処理になります。

スクリプト名:dialog3
set a to (display dialog “なんか入力して” buttons {“OK”} default button 1 default answer “”)
–> {button returned:”OK”, text returned:”あああああ”}
set bText to button returned of a –押されたボタン上のラベルが文字列で入る
–> “OK”
set iText to text returned of a –ダイアログに入力されたテキストが入る
–> “ああああ”

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/09/12 選択中のスクリーンショットから必要なもの以外削除

Shift-Command-3で作られる画面のスクリーンショットの画像ファイルを選択しておき、複数ディスプレイ接続時にできる「本来は必要ではない別のディスプレイ上のスクリーンショット」を削除するために作ったAppleScriptです。

Mac OS XではShift-Command-3で画面表示内容のスクリーンショットを画像に保存できます(常識レベルのお話)。これが、1つのディスプレイだけだと、

スクリーンショット 2014-09-12 13.01.59

といったファイル名で、デスクトップに日時を反映させたファイル名で作成されるわけですが、

bad_disp_arrange.png

といったように、複数台のディスプレイを接続している場合には、

・プライマリディスプレイ
スクリーンショット 2014-09-12 13.01.59

・セカンダリディスプレイ
スクリーンショット 2014-09-12 13.01.59(2)

といったように、プライマリ「以外の」ディスプレイのスクリーンショットにはその番号が末尾に付属します。OS Xでは6台までのディスプレイを認識するため、この数字の最大数は6ということになります。

きょうび、たいていのアプリケーションの表示は1台のディスプレイ内で完結していますので(昔みたいに、複数台のディスプレイにまたがった表示はしないので)、特定のアプリケーションのスクリーンショットをとっておきたい場合には、余計なディスプレイのスクリーンショットは「ゴミとして捨てる」場合がほとんどです。

そこで、デスクトップ上のスクリーンショットのファイルを選択しておいて、

screnshot1.png

本AppleScriptを実行。

screenshot2.png

どのディスプレイ用のスクリーンショットを残すか聞いてきますので、選択すると指定したディスプレイ以外のスクリーンショットはゴミ箱に移動されます。

Finder上で選択したファイルが「画面のスクリーンショットかどうか」の判定まではしていないので、不安であればそうした処理も追加するとよいでしょう。

スクリプト名:選択中のスクリーンショットから必要なもの以外削除
–選択中のファイル一覧をalias listで取得
tell application “Finder”
  activate
  
set aList to selection as alias list
end tell

–接続中のディスプレイをかぞえる
tell application “System Events”
  set dCount to count every desktop
end tell

–選択肢の作成
set dnList to {}
repeat with i from 1 to dCount
  set the end of dnList to (“(” & i as string) & “)” –全角カッコを指定「(」、[)」
end repeat

–残す項目を選ぶ
set aRes to choose from list dnList with prompt “どの画面のスクリーンショットを残しますか?” default items {contents of first item of dnList} with title “不要な画像をゴミ箱へ” without empty selection allowed
if aRes = false then
  display dialog “処理を中止します。” buttons {“OK”} giving up after 10 with title “キャンセルされました” with icon 2
  
return
end if

–選択された項目が何アイテム目かをサーチ(そういう命令があったかも、、、)
set aaRes to contents of first item of aRes

set aResNum to 1
repeat with i in dnList
  set j to contents of i
  
if j = aaRes then
    exit repeat
  end if
  
set aResNum to aResNum + 1
end repeat

–log {”aResNum”, aResNum}

if aResNum = 1 then
  –プライマリディスプレイ上のスクリーンショットを残すことを選択
  
set delList to (rest of dnList)
  
  
repeat with i in aList
    
    
tell application “Finder”
      set j to name of i
    end tell
    
    
–ファイル名に削除対象文字を含んでいるかどうかチェック
    
repeat with ii in delList
      set jj to (contents of ii) as string
      
–log {jj, j}
      
      
if j contains jj then
        tell application “Finder”
          delete i
        end tell
      end if
      
    end repeat
    
  end repeat
  
else
  –セカンダリディスプレイ上のスクリーンショットを残すことを選択
  
repeat with i in aList
    tell application “Finder”
      set j to name of i
    end tell
    
    
if j does not contain aaRes then
      tell application “Finder”
        delete i
      end tell
    end if
  end repeat
end if

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/09/10 InDesign CC 2014がアップデート、AppleScriptのバグ直らず

InDesign CC 2014がアップデート(10.0.0.70ビルド)しましたが、オブジェクトのpropertiesを取得できないバグは直っていません。

indd2014_buggy_yet.png

2014/09/09 各画面でムービーを全画面再生 v2

複数のQuickTimeムービーを、Macに接続した複数の外部モニターで各画面に全画面表示させつつ同時再生させるAppleScriptの試作品(実用性を追求したものではなく、趣味のプログラム)です。

あらかじめQuickTime Playerでムービーを開いておくと、Macに接続している各モニタの座標にムービーのウィンドウを移動させたうえで、各ムービー同時に全画面再生を行います。

r0016652.JPG

Macに複数のディスプレイが接続されている場合に、それらがどういった並びになっているのかは、AppleScriptからは分かりにくい状態です。そのため、すべての画面が横一列に近い状態で並んでいる場合のみを想定します(他の場合にはエラー)。

system_profiler経由で接続されているディスプレイの解像度一覧を取得し、横幅の総和を求めます。

別途、Finder経由でデスクトップの大きさを取得し(Finder経由では各モニタの解像度は分からない)、system_profiler経由で取得した横幅の総和と比較。これらが同じ場合(重複なく横に並んでいる場合)には処理を実施します。

disparrange.png
▲OK 各ディスプレイ上でのフルスクリーン再生を許容する場合のディスプレイの並び例

bad_disp_arrange.png
▲NG 各ディスプレイ上でのフルスクリーン再生を許容しない場合のディスプレイの並び例

各QuickTimeムービーのウィンドウを、各モニタに移動させ、各ムービー冒頭からフルスクリーンで再生を行います。

ところどころ、いまひとつな部分はありますが……想定していた機能はすべて(無理矢理)詰め込みました。本当は、ディスプレイの名称と座標をセットにしたリストを作りたかったのですが……これに、意外と苦戦。「とりあえずできればいい」程度に割り切りを行ったので形になりました。

スクリプト名:各画面でムービーを全画面再生 v2
–各ディスプレイの解像度を取得する
set aRes to retDisplayResolution() of me
–> {{1920, 1200}, {1920, 1200}, {1920, 1080}}
set monNum to length of aRes
–> 3

tell application “QuickTime Player”
  set dCount to count every document
end tell

–Error検出
if monNum < dCount then
  display dialog “ムービーをフル画面再生させるモニターの数が不足しています。”
  
return
end if

–トータルのデスクトップの大きさを取得
tell application “Finder”
  set {dX1, dY1, dX2, dY2} to get bounds of window of desktop
  
–> {-1920, 0, 3840, 1200}
end tell

–デスクトップの横方向のサイズを求める
set dX11 to absNum(dX1) of me
set dX21 to absNum(dX2) of me
set dXtotal to dX11 + dX21

–デスクトップの縦方向のサイズを求める
set dY11 to absNum(dY1) of me
set dY21 to absNum(dY2) of me
set dYtotal to dY11 + dY21

–モニター解像度を縦、横ともに合計する
set eX to 0
set eY to 0
repeat with i in aRes
  copy i to {aX, aY}
  
set eX to eX + aX
  
set eY to eY + aY
end repeat

—モニター配置パターンは、横に並んでいる場合のみを考慮するものとする(よわ、、、)
if eX is not equal to dXtotal then
  display dialog “モニタが想定どおりに並んでいないため、処理を終了します”
  
return
end if

–QuickTime Playerの各ウィンドウの移動
set mCount to 1
set startX to dX1

repeat with i in aRes
  copy i to {iX, iY}
  
  
–ウィンドウ移動
  
set eRes to changeBoundsOfQTX(startX, 32, mCount) of me
  
if eRes = false then
    display dialog “QuickTime Playerのウィンドウ操作でエラー”
    
return
  end if
  
  
set startX to startX + (absNum(iX) of me)
  
set mCount to mCount + 1
  
end repeat

–オープン中の全ムービーの同時、フル画面表示再生
tell application “QuickTime Player”
  activate
  
set current time of every document to 0
  
present every document
end tell

delay 2 –ムービー再生ウィンドウの全画面化UIアニメーションが終わるまで時間待ち

tell application “QuickTime Player”
  play every document
end tell

–指定のQuickTime Playerのウィンドウを指定の座標に移動
on changeBoundsOfQTX(aStartX, aStartY, aNum)
  tell application “QuickTime Player”
    set maxW to count every window
    
if aNum > maxW then return false –Error
    
    
tell window aNum
      set {mX1, mY1, mX2, mY2} to bounds
    end tell
    
    
set mXdiff to (mX2 - mX1)
    
set mYdiff to (mY2 - mY1)
    
    
tell window aNum
      set bounds to {aStartX, aStartY, aStartX + mXdiff, aStartY + mYdiff}
    end tell
  end tell
  
  
return true –OK
  
end changeBoundsOfQTX

–モニタ解像度を取得。複数モニタ接続時にはリストで列挙
on retDisplayResolution()
  
  
set sRes to do shell script “/usr/sbin/system_profiler SPDisplaysDataType | grep Resolution”
  
  
set sList to paragraphs of sRes
  
set resList to {}
  
  
repeat with i in sList
    set j to contents of i
    
set x1 to word 2 of j as number
    
set y1 to word 4 of j as number
    
set the end of resList to {x1, y1}
  end repeat
  
  
return resList
  
end retDisplayResolution

–絶対値を求める
on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/09/09 各画面でムービーを全画面再生

複数のQuickTimeムービーを、Macに接続した複数の外部モニターで各画面に全画面表示させつつ同時再生させるAppleScriptの試作品です。

同じオンライン対戦ゲーム(戦場の絆ポータブル)の内容をプレイヤーごとに視点を変えたムービーを(PSPエミュレータPPSSPP上でリプレイ再生させながらQuickTimeプレイヤーで画面キャプチャしつつ)作り、1つのディスプレイ上で同時再生させて見ていました。

c04dd845-6381-4b59-beb5-92f7157bd02b.jpg

ただ、せっかく外部モニタが3台つながっているので、3つのムービーを各ディスプレイに全画面表示させ同時に再生させようと考え、ちょっとAppleScriptを書いて試してみました。

r0016647.JPG

現状では、このAppleScriptだけでは動作は完結していません。

disparrange.png

ここでは、3つのムービーを各ディスプレイに1個ずつウィンドウを(手で)移動させる操作が必要で……すべてAppleScriptで動作を完結させるためには、接続されている各ディスプレイの解像度と位置関係をAppleScript側から把握する必要があります。

そこはまだ作っていないので、必要があれば調べる……といったところでしょうか。

スクリプト名:各画面でムービーを全画面再生
tell application “QuickTime Player”
  activate
  
set current time of every document to 0
  
present every document
  
play every document
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/09/03 ファイル名の冒頭部分でフォルダを作成してフォルダ分け

ファイル名が「xxxxx_yyyyyyyy.xxx」などとなっている場合に、ファイル名の冒頭部分の「xxxxx」でフォルダを新規作成し、ファイル名冒頭が同じファイルを作成したフォルダに移動させるAppleScriptです。

大量の画像を含むフリー画像素材集をダウンロードして、そのままだと整理しづらかったのでささっとScriptを書いて整理してみました。

finder_file_sep.png

……ささっと書いたのはよかったのですが、なぜか実行にやたらと時間がかかり…………OSのバグか? などと思ってみたものの、DiskUtility.appを実行してSSDの権限修復などをいったん行ってから実行したら、すぐに処理が終わるようになりました。

もしも、AppleScriptでファイル処理を行ってみて、異様なほど時間がかかる場合には(処理にもよりますが)、ディスクユーティリティーを実行してSSD/HDDのチェックを行ってみてください。

こういうScriptを書いて走らせていると、つくづくHDDの時代は終わったんだなーと感じます。

スクリプト名:ファイル名の冒頭部分でフォルダを作成してフォルダ分け
script spd
  
  
property aList : missing value –ファイルを入れる(alias list)
  
property cList : missing value –フォルダ名を入れる
  
end script

set aList of spd to {}
set cList of spd to {}

set aFol to choose folder with prompt “フォルダを選択してください”
set aFolStr to aFol as string

tell application “Finder”
  tell folder aFolStr
    set aList of spd to every file as alias list
  end tell
  
  
repeat with i in (aList of spd)
    
    
set j to contents of i
    
set aName to name of j
    
set fName to getFolNameBeforeSeparator(aName, “_”) of me
    
    
set tFol to aFolStr & fName & “:”
    
    
if fName is in (cList of spd) then
      –フォルダが存在する場合
      
ignoring application responses
        move j to folder tFol
      end ignoring
      
    else
      –フォルダが存在しない場合
      
set the end of (cList of spd) to fName
      
      
if not (exists of (folder tFol)) then
        set newFol to make new folder with properties {name:fName} at folder aFolStr
        
do shell script “sync”
      end if
      
      
ignoring application responses
        move j to folder tFol
      end ignoring
      
    end if
    
  end repeat
  
  
end tell

–与えられた文字列(たぶんファイル名)のうち、先頭から指定セパレータまでの間をフォルダ名として返す
on getFolNameBeforeSeparator(aName, aSeparator)
  set aLoc to offset of aSeparator in aName
  
if aLoc = 0 then
    return “”
  else
    set aStr to text 1 thru (aLoc - 1) of aName
    
return aStr
  end if
end getFolNameBeforeSeparator

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に