Archive for 4月, 2014

2014/04/30 破損画像チェック 10.9

破損画像チェック用AppleScriptをOS X 10.9向けに修正したものです。

ただ、Image Eventsの挙動がかなり(10.9からなのか、以前からなのか)変わっているようで、以前は検出できていた方法では、破損を検出できなくなっているようです。

たまたま破損した画像ファイル群(「破損画像コレクション」)を手元に収集しており、それぞれ、

(A)can not open

そもそも画像のオープン自体ができない

(B)alerted

(Photoshopで)画像オープン時に警告が表示されるが、オープンできる

(C)can not export

画像オープンできるが、QuickTime Playerにインポートしたあとでムービー保存しようとするとエラーになる

と破損レベルを定義して区分けしてきました。

以前は、(A)〜(C)までのデータをすべて検出できていたのですが、OS X 10.9のImage Eventsで同じデータに対してチェックを行ったところ、(A)のみ検出できる状態でした。何か、もう少し「まっとうな」方法で破損検出を行う必要がありそうです。

ただ、JPEG画像についてはファイル末尾のJPEGマーカーの存在確認だけでもけっこうなレベルで破損確認が行えると思います。

スクリプト名:破損画像チェック 10.9
set aFile to choose file
set aRes to breakImageCheck(aFile) of me
–> true/false (画像が破損しているとfalseが返る)

–破損画像チェック
–(通常時:true、破損時:false が返ってくる)
–対象形式:PICT/Photoshop/BMP/QuickTime Image/GIF/JPEG/MacPaint/JPEG2/SGI/PSD/TGA/Text/PDF/PNG/TIFF
on breakImageCheck(theFile)
  try
    tell application “Image Events”
      set this_image to open theFile
      
set {theWidth, theHeight} to dimensions of this_image
      
properties of this_image
      
close this_image
    end tell
    
    
return true –normal image
  on error
    return false –broken image
  end try
end breakImageCheck

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

2014/04/30 指定のクリエータコードに該当するICC Profileをリストアップ v3

Image Events経由で指定のクリエータコードに該当するICC ProfileをリストアップするAppleScriptです。

ICC Profileの名称を「ぴよぴよ」と返す場合と「ぴよぴよ.icc」と返す場合があって(作成時のメーカー側のゆらぎ)、そのあたりの整合性がないあたりを埋めるために強制的に名称の末尾に「.icc」を付けて返す機能や、名称から「.icc」を外して返す機能などを備えています。

ICC Profileの存在確認は、

 (1)ファイルとして指定のフォルダに存在するかという確認
 (2)OS側が認識しているかどうか(Image Eventsが認識しているか)という確認
 (3)アプリケーション側が認識しているかという確認

という3レイヤーで行う必要があり、それぞれについて取得できる名称が異なったりする(「.icc」が付いていたりいなかったり)ため、拡張子を付けたり外したりして結果を返す機能を付けたのがv2。

逆に、(2)で取得した名称を一切いじくらない機能を付けたのがこのv3です。

スクリプト名:指定のクリエータコードに該当するICC Profileをリストアップ v3
–出力例:クリエータがAppleのICC Profileを出力。重複を許容、ファイルパスを出力しない、末尾に強制的に「.icc」を付加
set aList to getICCProfileDataByCreatorCode(“APPL”, true, false, “”) of me
–> {”Calib 20100428″, “calib 20110623″, “calib20091122″, “Calibed_Retina_1″, “Cinema HD”, “Cinema HD Display”, “Cinema HD Display_L”, “Cinema HD 補正済み(R)”, “Color LCD 10.4.7″, “Color LCD 2009_09″, “colorLCD”, “Display”, “Display”, “Display”, “Display”, “Display”, “Display”, “DTW17ASXS_cailblated”, “HD 709-A”, “MacBook3″, “S2231W 補正済み”, “SD 170M-A”, “W2442 20090102″, “Web セーフカラー”, “カメラ RGB プロファイル”, “カラー LCD”, “グレイ調”, “セピア調”, “ディスプレイ”, “ブルー調”, “一般 CMYK プロファイル”, “一般 Lab プロファイル”, “一般 RGB プロファイル”, “一般 XYZ プロファイル”, “一般グレイガンマ 2.2 プロファイル”, “一般グレイプロファイル”, “白黒”, “明度減少”, “明度増大”}

–creatorで抽出したICC Profileのリストを取得する(名称、パス)
–  aCode: ICC ProfileのCreator Code(=”XXXX”)
–  dupFlag: trueだとICC Profileの名称重複を許容、falseだと許容しない
–   filePathOut: trueだとファイルパスを出力する
–  extOut:名称に拡張子(.icc)を出力するかどうか。falseだと出力しない。trueだと強制付加して返す、ヌルだとそのまま操作しない
on getICCProfileDataByCreatorCode(aCode, dupFlag, filePathOut, extOut)
  
  
–Image Eventsが起動中にICC Profileを追加すると、その後にProfileの存在確認を
  
–行うと認識されないケースがあったので、念のために起動中だったImage Eventsを落とす
  
tell application “Image Events”
    if it is running then
      quit
      
launch
      
delay 0.5 –待たないとダメ!!(MacProでは未検証)
    end if
  end tell
  
  
  
tell application “Image Events”
    try
      set aList to properties of every profile whose creator is aCode
    on error
      return {}
    end try
    
    
set dupList to {} –重複回避用リスト(名称ベース)
    
    
set outList to {} –出力用リスト
    
    
repeat with i in aList
      
      
set aName to (name of i)
      
      
if extOut = false then
        –拡張子を削って返す
        
if aName ends with “.icc” then
          set aName to text 1 thru -5 of aName
        end if
      else if extOut = true then
        –拡張子を強制的に追加して返す
        
if aName does not end with “.icc” then
          set aName to aName & “.icc”
        end if
      else
        –それ以外だと何もしない
      end if
      
      
set aLoc to (location of i)
      
      
try
        set aLoc to aLoc as alias
        
set aLoc to (aLoc as string)
      on error
        set aLoc to “”
      end try
      
      
      
if aLoc is not equal to “” then
        if dupFlag = false then
          –重複を許容しない
          
if aName is not in dupList then
            if filePathOut = true then
              set the end of outList to {aName, aLoc}
            else
              set the end of outList to aName
            end if
          end if
        else
          –重複を許容する
          
if filePathOut = true then
            set the end of outList to {aName, aLoc}
          else
            set the end of outList to aName
          end if
        end if
        
        
set the end of dupList to aName
        
      end if
    end repeat
    
  end tell
  
  
return outList
  
end getICCProfileDataByCreatorCode

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

2014/04/27 LAN上のHDDをマウントする

LAN上のMacのHDDをSMB/AFP経由でマウントするAppleScriptです。

# 最初に書いた内容に誤りがあったので、修正したうえでSMB経由でのマウントを確認しました

地味ですが、いざ調べ出すとサーバーURLの表記方法をあいまいにしか覚えていなかったりして困ることがあったのでまとめておきました。

サーバーのURLについては、とりあえずGUI経由で(Finderで)マウントしてみて、Finderの「情報を見る」コマンドで確認できます。

volume.png

「サーバ」のところに書かれているのがサーバーをマウントするためのURLです。

volumes.png

本Script内には通常実行の「mountVolume」と、非同期実行の「mountVolumeAsync」の2つを用意してみました。MacBook Pro Retinaから11インチのMacBook Airと13インチのMacBook Airの2台をIEE802.11nの無線LAN経由でマウントしてみたところ、「mountVolume」だと7秒、「mountVolumeAsync」だと0秒(1秒以下)でした。

ファイル共有まわりは、最近のMac OS X(OS X)で仕様が変わりつつあることを感じさせるところで……10.9ではファイル共有プロトコルとしてAFPにくわえ、SMB2が追加されデフォルトではSMB2が使用されているとか

afpsmb.jpeg

……このあたり、次のOS X 10.10以降でSMB2に一本化されそうな……ほかには10.9.x上ではAirDropをAppleScriptから制御できないので、このあたりも次の10.10で追加されることが予想されます。

# でも、現状では(Appleの)SMB2まわりはずいぶんきな臭い感じですね。ネット上でもいい話をあまり見かけない……

スクリプト名:LAN上のHDDをマウントする v2
–SMB経由でマウント
set userAccount to “maro” –ユーザー名(環境に応じて書き換え)
set userPass to “xxxxxx” –パスワード(環境に応じて書き換え)
set aMachine to “smb://MBA11._smb._tcp.local/Macintosh HD” –11インチMacBook Air (10.9.2)でSMBのみ有効にしてテスト
mountVolume(aMachine, userAccount, userPass) of me –通常実行

–AFP経由でマウント
set userAccount to “maro” –ユーザー名(環境に応じて書き換え)
set userPass to “xxxxxx” –パスワード(環境に応じて書き換え)
set aMachine to “afp://MBA13._afpovertcp._tcp.local/Macintosh SSD” –13インチMacBook Air (10.7.5)でAFPとSMBの両方を有効にしてテスト
mountVolumeAsync(aMachine, userAccount, userPass) of me –非同期実行

–指定のネットワーク上のディスクをマウントする
on mountVolume(aMachine, userAccount, userPass)
  
  
try
    with timeout of 100 seconds
      mount volume aMachine as user name userAccount with password userPass
    end timeout
  on error
    return false
  end try
  
  
return true
  
end mountVolume

–指定のネットワーク上のディスクをマウントする(非同期実行)
on mountVolumeAsync(aMachine, userAccount, userPass)
  
  
try
    ignoring application responses
      mount volume aMachine as user name userAccount with password userPass
    end ignoring
  on error
    
  end try
  
end mountVolumeAsync

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

2014/04/26 JANコードのチェックデジットの計算

JANコードのチェックサムを計算するAppleScriptです。

バーコードに使われている「JANコード」は13桁から構成されるコードであり、うち最初の12桁がデータで、最後の1桁はチェックサムです。

barcode.jpg

このチェックサムを計算するという、割とどうでもいいような感じのするものを手元にストックしていなかったので、やはりかなりどうでもいいような気もするものの、作っておきました。

スクリプト名:JANコードのチェックデジットの計算
set aData to “240469540245″ –女性用カジュアルパンプス(黒)
set aSum to calcJANcodeChkSum(aData)
–> 9

set aData to “978479731648″ –「Mac使いへの道」
set aSum to calcJANcodeChkSum(aData)
–> 3

–JANコードのチェックデジットの計算
–参照元:http://www.kabukoba.co.jp/info/barcode/check.htm
on calcJANcodeChkSum(aData)
  set aList to characters of aData
  
  
if length of aList is not equal to 12 then return false
  
  
set kiSnum to 0
  
set guNum to 0
  
  
repeat with i from 2 to length of aList
    
    
set kiSuF to ((i - 1) mod 2)
    
    
set j to (item i of aList) as number
    
    
if kiSuF = 1 then
      –奇数
      
set kiSnum to kiSnum + j
    else
      –偶数
      
set guNum to guNum + j
    end if
    
  end repeat
  
  
set kiSnum to kiSnum * 3
  
set guNum to guNum + (first item of aList) as number
  
  
set totalNum to kiSnum + guNum
  
  
set totalChar to totalNum as string
  
set lastDig to (last character of totalChar) as number
  
if lastDig is not equal to 0 then
    set sumNum to 10 - lastDig
  else
    set sumNum to 0
  end if
  
  
return sumNum
  
end calcJANcodeChkSum

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

2014/04/22 指定のディレクトリ構造のみ別の場所にコピー

テストデータの納品を行うのに、実データをまるごと送ると大変な容量になるので、まずは速報ベースでデータディレクトリ構造および検証結果ログデータをお送りする際に、元のテストデータからディレクトリ構造だけ別の場所に再現するときに使用したAppleScriptです。

納品時に全パラメータの組み合せでテスト演算を行い、そのテスト演算を複数のOS環境など異なる条件下で実施。照合についてはAppleScriptで全自動化することで、どんなに退屈でミスが許されない作業であっても安心して実施できるようになってきました。

testdir_copy.png

ただし、1万を超えるチェック画像データを生データのまま送るとデータ量が大きすぎてオンラインストレージでも心もとないので、軽いデータだけにして送ることに。テストディレクトリ構造だけ別の場所にコピーして、テキストログだけ手でコピー……という作業をやっていました。フォルダを手で作りはじめて、あまりの機械的な作業にイヤになり、この作業自体もAppleScriptに行わせることにしました。それがこのScriptです(前提が長いな〜)。

folcon.png

本AppleScript実行前に、コピー対象のフォルダをFinder上で選択しておく必要があります。複数フォルダを選択していた場合については考慮していないため、エラーになります。

なお、テストデータフォルダについては、すべてテスト内容を表した「英数字のみ」で表現していたので、日本語を含むフォルダでは実験していません。

スクリプト名:指定のディレクトリ構造のみコピー
script spd
  property aList : missing value
  
property bList : missing value
  
property cList : missing value
end script

set (aList of spd) to {}
set (bList of spd) to {}
set (cList of spd) to {}

tell application “Finder”
  set aFol to selection
  
if aFol = {} then return
end tell

set tFol to choose folder with prompt “ディレクトリ構造のコピー先を選択”
set tFolStr to tFol as string

tell application “Finder”
  set aFol to aFol as alias
  
set aFolStr to aFol as string
  
  
–指定フォルダ以下のファイル/フォルダをすべて取得
  
with timeout of 3600 seconds
    set (aList of spd) to (entire contents of folder aFol) as alias list
  end timeout
  
  
–Folderだけ収集
  
repeat with i in (aList of spd)
    set aPath to (contents of i) as string
    
if aPath ends with “:” then
      set the end of (bList of spd) to (contents of i)
    end if
  end repeat
  
  
–Folderを別の場所に掘る
  
repeat with i in (bList of spd)
    set bPath to (contents of i) as string
    
set cPath to repChar(bPath, aFolStr, tFolStr) of me
    
    
set cPathPosix to quoted form of POSIX path of cPath
    
    
–display dialog cPathPosix
    
    
–ディレクトリだけ作成
    
try
      do shell script “mkdir -p “ & cPathPosix –日本語のフォルダ名については未検証
    end try
    
  end repeat
  
end tell

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

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

2014/04/19 アプリケーションのBundle IDを取得する

指定アプリケーションのBundle IDを取得するAppleScriptです。

実用性は皆無ですが、AppleScriptのプログラムを書いている最中に指定アプリのバンドルIDを調べたくなったような場合に手軽に調べられます。

やりかたは超かんたんで……id of application “ほにゃらら”と書けば、指定したアプリケーションのBundle IDを取得できます。

歴代AppleScriptのリリースノートを調査していたら見つけました。知らなくても全然困らない機能ですが、「知らない」と言えない状況もあるので(汗)。

chooseapp.png

スクリプト名:アプリケーションのBundle IDを取得する
–アプリケーション一覧のダイアログを表示して選択(まずやらない)
set anApp to choose application –テスト用にアプリを選択
set anID to id of anApp –ここ!
–> “com.adobe.Photoshop”

–アプリケーションを直接指定してBundle IDを取得
set bID to id of application “Finder”
–> “com.apple.finder”

set cID to id of current application
–> “com.apple.ScriptEditor2″ –tellブロックがなければScript Editor

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

2014/04/16 指定のクリエータコードに該当するICC Profileをリストアップ v2

Image Events経由で指定のクリエータコードに該当するICC ProfileをリストアップするAppleScriptです。

以前に作ったものは一応名前の検出だけできればよかったので、ラフに書いた記憶がありますが……実際に実戦投入してみたところ、検出されないProfileがあるとかいろいろ問題が出てきたので、書き直してみました(以前のものはなかったことにしたいぐらいダメダメでした)。

また、ICC Profileの名称を「ぴよぴよ」と返す場合と「ぴよぴよ.icc」と返す場合があって(作成時のメーカー側のゆらぎ)、そのあたりの整合性がないあたりを埋めるために強制的に名称の末尾に「.icc」を付けて返す機能や、名称から「.icc」を外して返す機能などを備えています。

OSにICC Profileを強制的にインストールしたりと、ICC Profileの状態を変更した後には、Image Eventsを一度終了させないと最新の状態を検知してくれないことが判明して、「起動中ならいったん終了して再立ち上げ」といった処理を書いていますが……正直、開発環境よりも高速でCPUのキャッシングのメカニズムが異なるMacProでちゃんと動くのかは不明です。

おそらく、MacProでも動くとは思うのですが……こうして載せておいてAppleStoreの店頭のデモ機で試してみるしかないですね。

スクリプト名:指定のクリエータコードに該当するICC Profileをリストアップ v2
–出力例:クリエータがAppleのICC Profileを出力。重複を許容、ファイルパスを出力しない、末尾に強制的に「.icc」を付加
set aList to getICCProfileDataByCreatorCode(“APPL”, true, false, true) of me
–> {”Calib 20100428.icc”, “calib 20110623.icc”, “calib20091122.icc”, “Calibed_Retina_1.icc”, “Cinema HD.icc”, “Cinema HD Display.icc”, “Cinema HD Display_L.icc”, “Cinema HD 補正済み(R).icc”, “Color LCD 10.4.7.icc”, “Color LCD 2009_09.icc”, “colorLCD.icc”, “Display.icc”, “Display.icc”, “Display.icc”, “Display.icc”, “Display.icc”, “Display.icc”, “DTW17ASXS_cailblated.icc”, “HD 709-A.icc”, “MacBook3.icc”, “S2231W 補正済み.icc”, “SD 170M-A.icc”, “W2442 20090102.icc”, “Web セーフカラー.icc”, “カメラ RGB プロファイル.icc”, “カラー LCD.icc”, “グレイ調.icc”, “セピア調.icc”, “ディスプレイ.icc”, “ブルー調.icc”, “一般 CMYK プロファイル.icc”, “一般 Lab プロファイル.icc”, “一般 RGB プロファイル.icc”, “一般 XYZ プロファイル.icc”, “一般グレイガンマ 2.2 プロファイル.icc”, “一般グレイプロファイル.icc”, “白黒.icc”, “明度減少.icc”, “明度増大.icc”}

–creatorで抽出したICC Profileのリストを取得する(名称、パス)
–  aCode: ICC ProfileのCreator Code(=”XXXX”)
–  dupFlag: trueだとICC Profileの名称重複を許容、falseだと許容しない
–   filePathOut: trueだとファイルパスを出力する
–  extOut:名称に拡張子(.icc)を出力するかどうか。falseだと出力しない。trueだと強制付加して返す
on getICCProfileDataByCreatorCode(aCode, dupFlag, filePathOut, extOut)
  
  
–Image Eventsが起動中にICC Profileを追加すると、その後にProfileの存在確認を
  
–行うと認識されないケースがあったので、念のために起動中だったImage Eventsを落とす
  
tell application “Image Events”
    if it is running then
      quit
      
launch
      
delay 0.5 –待たないとダメ!!(MacProでは未検証)
    end if
  end tell
  
  
  
tell application “Image Events”
    try
      set aList to properties of every profile whose creator is aCode
    on error
      return {}
    end try
    
    
set dupList to {} –重複回避用リスト(名称ベース)
    
    
set outList to {} –出力用リスト
    
    
repeat with i in aList
      
      
set aName to (name of i)
      
      
if extOut = false then
        –拡張子を削って返す
        
if aName ends with “.icc” then
          set aName to text 1 thru -5 of aName
        end if
      else
        –拡張子を強制的に追加して返す
        
if aName does not end with “.icc” then
          set aName to aName & “.icc”
        end if
      end if
      
      
set aLoc to (location of i)
      
      
try
        set aLoc to aLoc as alias
        
set aLoc to (aLoc as string)
      on error
        set aLoc to “”
      end try
      
      
      
if aLoc is not equal to “” then
        if dupFlag = false then
          –重複を許容しない
          
if aName is not in dupList then
            if filePathOut = true then
              set the end of outList to {aName, aLoc}
            else
              set the end of outList to aName
            end if
          end if
        else
          –重複を許容する
          
if filePathOut = true then
            set the end of outList to {aName, aLoc}
          else
            set the end of outList to aName
          end if
        end if
        
        
set the end of dupList to aName
        
      end if
    end repeat
    
  end tell
  
  
return outList
  
end getICCProfileDataByCreatorCode

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

2014/04/15 現在のマシン上の全ユーザーアカウントを取得して、管理者権限を持っているものだけを返す

Scriptを実行中のマシン上の全ユーザーアカウントを取得して、そのうち管理者権限を持っているアカウントだけを抽出してリストで返すAppleScriptです。

さるアプリケーションの起動プロセスで、システムにあるファイルが存在していない場合にはインストールを行うようにすることになり……Objective-Cでやると大変なので、AppleScriptで行うことに。

# インストーラーを作成すれば、このあたりは自動でやってもらえるのですが
# インストーラーを作成するためにはCodeSignが必要で、お客様にその手間を
# かけていただくのは物理的に無理だったので、こんなイバラの道を……

do shell scriptには管理者パスワードを指定することで管理者権限で実行できる(with administrator privileges)ので、安直にdo shell scriptでやればよい……そんな風に考えていた時代もありました。しかし、現在ログイン中のユーザーが管理者権限を持っている場合にはそれでよいのですが、管理者権限を持っていないユーザーでログイン中だったらどうするのか?

do shell scriptを管理者権限で実行するためには、ユーザー名も指定しなければなりません。それは……調べてユーザーに提示しないと、分らないわけで………

仕方なく、いろいろ探しては組みはじめるわけですが……その結果、こんな感じになりました。いや、当初想定していたよりも大変なこと大変なこと。15分ぐらいかかってしまいました。

スクリプト名:現在のマシン上の全ユーザーアカウントを取得して、管理者権限を持っているものだけを返す
set adList to getAdminUserNames()
–> {”maro”, “piyo”}

–現在のマシン上の全ユーザーアカウントを取得して、管理者権限を持っているものだけを返す
on getAdminUserNames()
  –マシン上の全ユーザーアカウントのユーザー名を取得
  
tell application “System Events”
    set userNameList to name of every user
  end tell
  
  
–全ユーザー名のうち、管理者権限を持っているものだけを抽出
  
set adminNames to {}
  
repeat with i in userNameList
    set j to contents of i
    
set uRes to getUsersPrivileges(j)
    
if uRes = true then
      set the end of adminNames to j
    end if
  end repeat
  
  
return adminNames
end getAdminUserNames

–現在実行中のユーザーの権限を得る(管理者か、それ以外か) 10.4および10.5以降両用
–管理者だとtrueが、それ以外だとfalseが返る
on getUsersPrivileges(aUser)
  set aVer to system attribute “sys2″ –OSメジャーバージョンを取得する(例:Mac OS X 10.6.4→6)
  
  
if aVer > 4 then
    –Mac OS X 10.5以降の処理
    
set adR to (do shell script “/usr/bin/dsmemberutil checkmembership -U “ & aUser & ” -G admin users”)
    
    
if adR = “user is a member of the group” then
      return true
    else
      return false
    end if
    
  else
    –Mac OS X 10.4までの処理
    
set admin_users to (do shell script “/usr/bin/niutil -readprop . /groups/admin users”)
    
tell (a reference to AppleScript’s text item delimiters)
      set {old_atid, contents} to {contents, ” “}
      
set {admin_users, contents} to {text items of admin_users, old_atid}
    end tell
    
    
if aUser is in admin_users then
      return true
    else
      return false
    end if
  end if
  
end getUsersPrivileges

–現在実行中のユーザーの権限を得る(管理者か、それ以外か) 10.4および10.5以降両用
–管理者だとtrueが、それ以外だとfalseが返る
on getCurUsersPrivileges()
  set aVer to system attribute “sys2″ –OSメジャーバージョンを取得する(例:Mac OS X 10.6.4→6)
  
  
set current_user to (do shell script “whoami”) –実行中のユーザー名を取得
  
  
if aVer > 4 then
    –Mac OS X 10.5以降の処理
    
set adR to (do shell script “/usr/bin/dsmemberutil checkmembership -U “ & current_user & ” -G admin users”)
    
    
if adR = “user is a member of the group” then
      return true
    else
      return false
    end if
    
  else
    –Mac OS X 10.4までの処理
    
set admin_users to (do shell script “/usr/bin/niutil -readprop . /groups/admin users”)
    
tell (a reference to AppleScript’s text item delimiters)
      set {old_atid, contents} to {contents, ” “}
      
set {admin_users, contents} to {text items of admin_users, old_atid}
    end tell
    
    
if current_user is in admin_users then
      return true
    else
      return false
    end if
  end if
  
end getCurUsersPrivileges

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

2014/04/10 iWork ‘13の2回目のアップデートでAppleScript対応機能が大幅強化、か?

先日行われた、iWork ‘13のKeynote v6.2/Pages v5.2/Numbers v3.2へのアップデートで、AppleScriptの機能が大幅に強化されました。

Keynoteについていえば、iWork ‘09の頃のAppleScript対応度は冗談のようなもので、ドキュメントの生成も、ドキュメント上のオブジェクトの生成も、オブジェクトへの属性値の設定(表を作るとか)も、ほぼ何もできない状態でした。

それが、ひととおりドキュメントからスライド(各ページ)から各オブジェクトまで不完全ながらもAppleScriptから生成できるようになり、完全ではないもののデータの取得もできるようになりました。従来どおり、スライド表示のコントロールも行えます。

ただし、AppleScript対応機能で大事な点が抜けており……それは、selectionが取れないことです。selectionでもselected objectでもよいのですが、GUI上で作業を行っている、まさにその対象に対してScriptを実行したい場合が往々にしてあるわけで、たとえば選択中のテキストボックスにデータを入れるとか、選択中の表に対してデータを入れるとか、そういう作業は一切できません。

その点だけ割り切れば、これまでのダメダメだったAppleScript対応度がウソのように強化されており、あと2回ぐらいのアップデートを経て日常的なAppleScript系作業で不満が出ないレベルが実現されそうな気配がします(まだ完成していないという認識を持つことが重要です)。

iWork 13のこれまでのバージョンは、機能とAppleScript対応度を調査して「即・ゴミ箱行き」「使う価値なし」だったのですが、今回のバージョンからは「ちょっと我慢して使ってみようか」と思えるようになりました。

ゴミにしか見えなかったKeynote v6やv6.1に比べれば、格段の進歩といえます。

今回のアップデート全般

AppleScript用語辞書が、段違いに改善されました。これまで、exportコマンドやsaveコマンドでは拡張子を指定するのしないのとで保存ができたりできなかったりしていましたが、そうした重要な情報がAppleScript用語辞書に含まれるようになりました。

それらの情報を補足するためAppleScript用語辞書に「表」が含まれるようになり、(部分的に)サンプルスクリプトが掲載されるようになり、20年におよぶAppleScript史上はじめてAppleの「本気」が垣間見られたような気がします。

key_table.png

key_sample.png

ただ、「本気」といってもしょせんはAppleの本気なので、ぱっと見はいいのですが……仔細に調べていくと、アラが随所に見られます。

「可能性を感じさせるものの、未完成」のひとことに尽きます。

Keynote v6.2

document

まずは、ドキュメントを作成して……いきなりつまずきます。AppleScript用語辞書のサンプルスクリプトには、ドキュメント作成時にdocument widthとdocument heightが指定できるように書かれているのですが、これは辞書に書かれているサンプルが思いっきり間違っています。

key_doc_prop.png

heightとwidthを指定するようにしてください。documentオブジェクトの説明箇所にはきちんとwidthとheightと書かれているので、校正不足といったところでしょうか。

教訓:EXAMPLE SCRIPTは参考にはなるが、そのままでは使えない可能性がものすごく高い

スクリプト名:Keynote 6.2でドキュメントを新規作成
tell application “Keynote”
  set adoc to (make new document with properties {document theme:theme “ホワイト”, height:768, width:1024})
  
  
–AppleScript用語辞書のサンプルには「document heiight」「document width」と書かれているが、
  
–明らかに間違い。今後もたぶんこのまま。
  
  
properties of adoc
  
–> {name:”名称未設定 3″, class:document, height:768, auto restart:false, maximum idle duration:15, width:1024, file:missing value, modified:true, slide numbers showing:false, current slide:slide 1 of document “名称未設定 3″ of application “Keynote”, auto play:false, document theme:theme id “Application/White/Standard” of application “Keynote”, auto loop:false}
end tell

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

theme

document作成時にテーマ種別を指定できるようになっており、日本語環境では日本語で「ホワイト」とか「グラデーション」と指定するようになっています(各国語環境間でScriptの互換性がなくなるんですが、いいんでしょうか? 割と、iTunesで問題になったのですが、、、)。

テンプレートの呼び名が無駄にローカライズされているということで、例によってAppleScript用語辞書中のサンプルスクリプトはそのままでは動きません。

スクリプト名:Keynote 6.2でインストールされているテーマ名称一覧を取得
tell application “Keynote”
  set the installedThemes to the name of every theme
  
–> {”ブラック”, “ホワイト”, “グラデーション”, “写真エッセイ”, “クラシック”, “スレート”, “クリームペーパー”, “アーティザン”, “即興”, “ショールーム”, “ルネッサンス”, “写真ポートフォリオ”, “論説”, “京都風”, “ブラシ塗りキャンバス”, “活字組”, “モロッコ風”, “クラフト”, “インダストリアル”, “モダンポートフォリオ”, “ハーモニー”, “方眼紙”, “ブループリント”, “フォーマル”, “革装本”, “ビンテージ”, “ハードカバー”, “布装本”, “黒板”, “羊皮紙”}
end tell

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

master slide

各スライド(ページ)に対してマスタースライドを適用する際、若干注意を要するようです。

何も考えないで書くとエラーが出ます。master slideの名称もローカライズされており、AppleScript中でも日本語でmaster slide名を表記します。このため、名称を直接記述するとScriptの言語間での互換性がなくなります(英語環境など他の言語環境ではエラーになる)。

スクリプト名:Keynote 6.2で最前面のドキュメントで指定可能なマスタースライドの名称を取得
tell application “Keynote”
  tell front document
    set the installedMaster to the name of every master slide
    
–> {”タイトル & サブタイトル”, “画像(横長)”, “タイトル(中央)”, “画像(縦長)”, “タイトル(上)”, “タイトル&箇条書き”, “タイトル、箇条書き、画像”, “箇条書き”, “画像(3点)”, “引用”, “写真”, “空白”}
    
  end tell
end tell

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

slide

現在表示中のスライドは「current slide」と表記できます。

AppleScript用語辞書中のサンプルスクリプトを見ると、現在表示中のスライドの前後や、ドキュメントの末尾にスライドの追加が可能です。ただし、サンプルスクリプトは例によって、くどく、原理主義的に書いてあるので(英文的な読みやすさの確保のために「the」が書いてあるとか、わざわざ暗黙のget命令が書いてあるとか)……逆に、かなり分りづらい印象を受けます(クリス君のScriptは読みにくいです)。

スクリプト名:Keynote 6.2でスライド(ページ)のマスターを変更
–サンプルScriptとして紹介されていた記述例
tell application “Keynote”
  tell front document
    set base slide of current slide to master slide “空白”
  end tell
end tell

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

スクリプト名:Keynote 6.2でスライド(ページ)のマスターを変更2(エラーになる)
–エラーになる記述例
tell application “Keynote”
  tell front document
    tell current slide
      set base slide to master slide “空白”
    end tell
  end tell
end tell

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

スクリプト名:Keynote 6.2でスライド(ページ)のマスターを変更3
–エラーを回避しつつ、オブジェクト階層構造を意識した書き方に変更
tell application “Keynote”
  tell front document
    set aMaster to master slide “空白”
    
    
tell current slide
      set base slide to aMaster
    end tell
  end tell
end tell

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

audio clip

GUI上で、Keynoteの書類に音声ファイルをドラッグ&ドロップするとaudio clipが作成できます。GUI上で作成したaudio clipから各種プロパティを取得することはできるのですが、AppleScript側から作成を試みてはいるものの、まだできていません。多分、現段階の実装では無理なのだと思います(あるいは、Keynoteの書類のバンドル内に無理矢理音声ファイルを入れてなんとかするとか)。

スクリプト名:Keynote 6.2で最前面のドキュメントの現在のスライドにaudio clipを新規作成(未遂)
set aClip to choose file

tell application “Finder”
  set aName to name of aClip
end tell

tell application “Keynote”
  tell front document
    tell current slide
      make new audio clip with properties {file name:aName, position:{669, 165}, height:0, repetition method:none, locked:false, width:0, clip volume:100} with data aClip
    end tell
  end tell
end tell

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

shape

AppleScriptからの作成、情報取得が可能です。ただし、現時点の実装では作成できるのが「四角形」だけで、shapeの種類は指定できませんし取得もできません。

塗りのタイプ(background fill type)は取得できるものの、何色で塗ってあるのかなど詳細な情報はまだ取得できません。

chart

作成すると棒グラフができるのですが、グラフの種別やデータ内容を指定できません。

サイズと位置と回転方向や反射エフェクトがかかっているか、ぐらいの内容です。

image

目下、作成できません。作成できますが、GUI側からドラッグ&ドロップで配置・作成したimageのプロパティを取得できません。

スクリプト名:Keynote v6.2でimageを配置する
tell application "Keynote"
  –画像ファイルの選択
  
set thisImageFile to choose file
  
  
tell front document
    tell current slide
      – 画像ファイルの追加
      
set thisImage to make new image with properties {file:thisImageFile}
      
      
–properties of thisImage
      
–imageのプロパティを一括して取得できないのは相変わらず
      
–個別には取得できる
    end tell
  end tell
end tell

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

group

「groupという図形」として扱われるようです。位置情報やサイズは取得できるのですが、groupそのものをAppleScriptから作成することができません。当然、グループの解除機能もありません。

line

既存のlineのプロパティの取得は可能ですが、そもそも生成ができません。

movie

audio clipと同じ

table

今回、唯一のまともなオブジェクトです。作成可能で、プロパティの取得も行えます。

スクリプト名:Keynote 6.2で表を作成してプロパティを取得
tell application “Keynote”
  tell front document
    tell current slide
      set aTable to (make new table)
      
properties of aTable
      
–> {locked:false, cell range:range “A1:D5″ of table 1 of slide 1 of document “名称未設定 2″ of application “Keynote”, column count:4, parent:slide 1 of document “名称未設定 2″ of application “Keynote”, header column count:1, footer row count:0, class:table, position:{100, 100}, width:824, name:”表 1″, selection range:range “A1:D5″ of table 1 of slide 1 of document “名称未設定 2″ of application “Keynote”, header row count:1, height:568, row count:5}
    end tell
  end tell
end tell

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

スクリプト名:Keynote 6.2で最前面のドキュメントの現在のスライドの表にデータを入れる
set aData to {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0}
set aCount to 1

tell application “Keynote”
  tell front document
    tell current slide
      tell table 1
        set cList to every cell
        
repeat with i in cList
          set j to contents of i
          
          
tell j
            set value to (contents of (item aCount of aData))
          end tell
          
          
set aCount to aCount + 1
        end repeat
      end tell
    end tell
  end tell
end tell

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

text item

作成可能で、プロパティの取得も行えます。ただし、object text属性に内容がリッチテキストで入っているとあるものの、現段階では違うようです(プレーンテキストっぽいです)。

exportコマンド

指定ドキュメントのPDF書き出しを確認しました。

スクリプト名:Keynote 6.2でexport
set aExportPath to choose file name

tell application “Keynote”
  tell front document
    export to aExportPath as PDF
    
  end tell
end tell

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

saveコマンド

名称未設定の未保存ドキュメントを指定のパスに保存できることを確認しました。

ただし、「as Keynote」オプションを付けるとまともに動作しないようで……いまひとつ意味が分りません。

スクリプト名:Keynote 6.2でsave
set aExportPath to choose file name
–set aExportPathStr to aExportPath as string

tell application “Keynote”
  tell front document
    save in aExportPath
    
  end tell
end tell

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

総評

最初見たときには、「今回は90点ぐらいいっちゃうかも?」と驚いたのですが、忙しくてすぐに(まともな)評価ができず……腰をすえて評価を行っていくと、あれもダメこれもダメ……といった具合いで、まだまだ「作業途中」の印象が強いです。将来への期待をこめて60点といったところでしょうか(ここで作業が停まったら10点ぐらい)。

ただ、Keynote v5.xまでのダメダメさに比べれば、v6.x完成の暁には素晴らしい出来になるでしょうし、v6.1とも完全に別物です。当初から、「半年をメドに強化を行う」と表明していたので、本当にそのぐらいかかることでしょう。

これまでの「AppleScript対応度はApple純正アプリが一番弱い」という定評を今後覆すまでになるのか、あるいは集中力が持続せずCalendar.app(旧称:iCal)のように「ダメアプリ」道を驀進することになるのか……

大規模なドキュメント(画面図を大量に含む仕様書とか)については、あまりにKeynote v5.xまでのAppleScript対応度が低かったために、OmniGraffleに移行してOmniGraffle上で画面図の自動更新やインデックスの自動生成などのプログラムを作って運用していました。

Keynote v6.xのAppleScript対応機能がまともになれば、それらのシステムもOmniGraffleではなくKeynote上で運用することができるようになることでしょう。

2014/04/07 Radikoで選局 v4

SafariでRadiko.jpをオープンして現在の地域で選局可能なラジオ局の一覧を取得し、一覧から選択したあとに、指定したラジオ局をオープンするAppleScriptです。

自作のAppleScriptObjCのアプリケーション「radiRec」(あくまで個人用)に組み込んで、指定時刻に指定ラジオ局の番組を録音するために作成したものです。

Radiko.jpのプレミアム会員サービス(エリアフリー聴取)がはじまり、Webサイトの構成も若干変更されたため、ラジオ局のリスト作成および選択用のプログラムも変更が必要になりました。

GUI Scripting経由でSafariにアクセスしているので、システム環境設定からあらかじめ「アクセシビリティ」から「補助装置にアクセスできるようにする」にチェックを入れておくか、Script実行時にパスワードを入力すると設定を変更します。

radiko41.png

OS X 10.9.2+Safari 7.0.3、場所は東京都内で動作確認してあります(他のエリアではチェックしていません)。

スクリプト名:Radikoで選局 v4
–東京エリアのRadikoのステーションリストを取得、指定のステーションを選局
–OS X 10.9.2(本当は3だけど), Safari 7.0.3, Radiko.jpがプレミアム会員サービスを開始した状態に対応

property waitSec : 10 –Safariのページローディングを待つ時間(秒)

–GUI Scriptingの確認
repeat 3 times
  tell application “System Events”
    set aRes to UI elements enabled
  end tell
  
  
if aRes = false then
    display dialog “GUI Scriptingをオンにしてください。” buttons {“OK”} default button 1
    
tell application “System Events”
      activate
      
set UI elements enabled to true
    end tell
  end if
end repeat
if aRes = false then return

set aList to getRadioStationList() of me
–> {”TBSラジオ”, “文化放送”, “ニッポン放送”, “ラジオNIKKEI第1″, “ラジオNIKKEI第2″, “InterFM”, “TOKYO FM”, “J-WAVE”, “ラジオ日本”, “bayfm78″, “NACK5″, “FMヨコハマ”, “放送大学”}

tell current application
  activate
  
set aRes to choose from list aList
end tell

set aaRes to first item of aRes

selectRadioStation(aaRes) of me –選曲

–SafariでRadikoの選局を行う
on selectRadioStation(aStation)
  activate application “Safari”
  
  
tell application “Safari”
    close every document
    
    
tell document 1
      tell current tab
        open location “http://radiko.jp”
      end tell
    end tell
  end tell
  
  
delay waitSec
  
  
tell application “System Events”
    tell process “Safari”
      tell window 1
        –おきにいりバーの表示/非表示に対応
        
set countGroup to count every group
        
        
tell group countGroup
          tell group 1
            tell group 1
              tell scroll area 1 –Scrollエリア
                tell UI element 1 –HTMLコンテンツ
                  tell list 2
                    –ログイン/プレミアム会員登録の箇所が増えたのでlist2に
                    
set gList to every group
                    
                    
set uList to {}
                    
                    
repeat with i in gList
                      tell i
                        tell group 1 –ここ、1階層増えた
                          set a to UI element 1
                          
set statName to title of a
                          
set the end of uList to statName
                          
if statName = aStation then
                            
                            
click a
                            
                          end if
                        end tell
                      end tell
                    end repeat
                    
                  end tell
                end tell
              end tell
            end tell
          end tell
        end tell
      end tell
    end tell
  end tell
  
end selectRadioStation

–SafariでRadikoのラジオ局一覧を取得する
on getRadioStationList()
  activate application “Safari”
  
  
tell application “Safari”
    close every document
    
    
tell document 1
      tell current tab
        open location “http://radiko.jp”
      end tell
    end tell
  end tell
  
  
delay waitSec
  
  
tell application “System Events”
    tell process “Safari”
      tell window 1
        –おきにいりバーの表示/非表示に対応
        
set countGroup to count every group
        
        
tell group countGroup
          tell group 1
            tell group 1
              tell scroll area 1 –Scrollエリア
                tell UI element 1 –HTMLコンテンツ
                  tell list 2
                    –ログイン/プレミアム会員登録の箇所が増えたのでlist2に
                    
set gList to every group
                    
                    
set uList to {}
                    
                    
repeat with i in gList
                      tell i
                        tell group 1 –ここ、1階層増えた
                          set a to UI element 1
                          
set statName to title of a
                          
set the end of uList to statName
                        end tell
                      end tell
                    end repeat
                    
                    
return uList
                    
                  end tell
                end tell
              end tell
            end tell
          end tell
        end tell
      end tell
    end tell
  end tell
  
end getRadioStationList

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

2014/04/01 なんでもデータを文字列化 v3

AppleScriptがネイティブでもっているデータ型(integer, number, real, string, text, Unicode text, list, record、boolean、missing value)を文字列化するAppleScriptです。

# 本来、AppleScriptにはlistやrecordをそのままテキストにcastする機能はありません

掲載したプログラム(v2)で、リストに入れたboolean(true/false)への対応が抜けていたので追加したものです。

本Scriptはいろいろ便利に使っていますが、一番有用だったのは……データの内容をテキストファイルにログ書き出しする際に、レコードからいちいち属性ごとにラベルを指定して取り出してテキスト化するのではなく、まるごとテキスト化することで大幅に処理の記述を省略できた、というケースです。

全国規模のネットワーク上に構築されたファイルサーバー群に対して各種PDF出力をMacクライアント側からの指定どおりに行う、というけっこう規模の大きいクライアント/サーバーのシステムをすべてAppleScript(当時はAppleScript Studio)で開発したことがあり、サーバー側でワークログだったりエラーログをテキスト保存する際に、このような処理を用いました。

スクリプト名:なんでもデータを文字列化 v3

set a to {true, false, “test string”} –listed boolean
–set a to {{true, false}, {true, true}, {false, false}} –nested listed boolean
–set a to {{aName:”PiyoPiyo”, anAge:10}, {aName:”Piyoko”, anAge:9}} –record in list
–set a to {aName:”PiyoPiyo”, anAge:10}–record
–set a to {{1, 2, 3}, {4, 5, 6}}–list
–set a to 1.0 as real–real
–set a to 1 as integer–integer
–set a to “1.0″ as string–string
–set a to true–boolean
–set a to front window of application “Finder”–ERROR
–set a to missing value

set aRes to convToStr(a) of somethingToStrKit
–> “{true, false}”

–リストでもレコードでもなんでも文字列化して返すキット
script somethingToStrKit
  
  
on convToStr(aRec)
    
    
set aClass to (class of aRec) as string
    
    
if (aClass = “integer”) or (aClass = “number”) or (aClass = “real”) or (aClass = “string”) or (aClass = “text”) or (aClass = “Unicode text”) or (aClass = “boolean”) then
      set aRes to aRec as string
    else if aClass is “list” then
      set aRes to listToString(aRec)
    else if aClass is “record” then
      set aRes to recToString(aRec)
    else
      try
        set aRes to aRec as string
      on error
        –アプリケーションのオブジェクトとかはエラーで返す
        
return false
      end try
    end if
    
    
return aRes
    
  end convToStr
  
  
  
–レコードをStringに変換
  
  
–エラートラップを使って、わざとエラーを発生させ、エラーメッセージからレコードをstringに変換する
  
on recToString(aRec)
    
    
–レコードを無理矢理stringにcastして、エラーメッセージを取得する
    
try
      set a to aRec as string –ここでエラー発生
    on error aMes
      set a to aMes
    end try
    
    
–エラーメッセージ文字列から、元のレコードの情報を組み立てる
    
set b to trimStrFromTo(a, “{”, “}”)
    
set b to “{” & b & “}”
    
    
return b
    
  end recToString
  
  
  
on trimStrFromTo(aStr, fromStr, toStr)
    –fromStrは前から探す
    
if fromStr is not equal to “” then
      set sPos to (offset of fromStr in aStr) + 1
    else
      set sPos to 1
    end if
    
    
–toStrは後ろから探す
    
if toStr is not equal to “” then
      set b to (reverse of characters of aStr) as string
      
set ePos to (offset of toStr in b)
      
set ePos to ((length of aStr) - ePos)
    else
      set ePos to length of aStr
    end if
    
set aRes to text sPos thru ePos of aStr
    
    
return aRes
    
  end trimStrFromTo
  
  
  
–リストおよびリストに入ったレコードをStringに変換
  
  
on listToString(aList)
    set listText to {“{”}
    
set quotChar to ASCII character 34
    
set firstFlag to true
    
    
repeat with i in aList
      set j to contents of i
      
set aClass to (class of i) as string
      
if (aClass = “integer”) or (aClass = “number”) or (aClass = “real”) or (aClass = “boolean”) then
        set the end of listText to (getFirst(firstFlag) of me & j as text)
        
set firstFlag to false
      else if (aClass = “string”) or (aClass = “text”) or (aClass = “Unicode text”) then
        set the end of listText to ((getFirst(firstFlag) of me & quotChar & j as text) & quotChar)
        
set firstFlag to false
      else if aClass is “list” then
        set the end of listText to (getFirst(firstFlag) & listToString(j)) –ちょっと再帰処理
        
set firstFlag to false
      else if aClass is “record” then
        set the end of listText to (getFirst(firstFlag) & recToString(j))
        
set firstFlag to false
      end if
    end repeat
    
    
set the end of listText to “}”
    
set listText to listText as text
    
    
return listText
  end listToString
  
  
on getFirst(aFlag)
    if aFlag = true then return “”
    
if aFlag = false then return “, “
  end getFirst
  
end script

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