Automator in Snow Leopard - テキスト編

(個人的に)わりと評価が高い Automator。Snow Leopard で何が変わったかといえば、サービスを作成することができるようになったということだけなのですが、これが地味に便利。コンテクストメニュー地味に便利。

そんなわけで勢いに任せて、作ってみました。

インストーラーパッケージが解凍されるので、あとはダブルクリックしてインストールしてください。いつものことですが、ご利用はご自身の責任でお願いします。

収録されている内容は以下の通りです。

  • Finder • Insert Selections Name

Finder で選択されている項目の名前を挿入します。

  • Finder • Insert Selections POSIX path

Finder で選択されている項目の POSIX パスを挿入します。

  • iTunes • Insert Current Track

iTunes で現在再生されている曲名を挿入します。

  • iTunes • Now Playing

Twitter でよく見かけるようなフォーマット(Now Playing: [Artist] - [Track name])で iTunes で現在再生されている曲名を挿入します。

  • iTunes • Search Artist in ITS

選択されているテキストをアーティスト名としてい iTunes Store で検索を行います。

  • Safari • Insert Current URL

Safari で現在閲覧している Web ページの URL を挿入します。

  • Text • Add Line Number

行頭に 3 桁の行番号を追加します。

  • Text • Add Prefix

行頭に引用符(>)を追加します。

  • Text • Appley Markdown

選択されているテキストに Markdown を適用します。Markdown.pl が /usr/local/bin にインストールされている必要があります。

  • Text • Evaluate Date Format

UNIX の date コマンドで利用できる日時フォーマットを評価します。例えば、%Y-%m-%d なら、2010-05-22 になります。フォーマットについては Terminal で strftime のマニュアルを参照してみてください。

  • Text • Force Line Break

選択されているテキストを 80 文字で強制改行します。

  • Text • Insert Current Date

今日の日付を挿入します。実行するとダイアログでデータのフォーマットを尋ねます。UNIX の date コマンドで利用できるフォーマットを指定してください。デフォルトでは %FT%T%z (2010-05-22T00:10:59+0900)が指定されています。フォーマットについては Terminal で strftime のマニュアルを参照してみてください。

  • Text • Insert MIT License

MIT License を挿入します。現在の年とログインしているユーザーの名前が利用されます。

  • Text • Postal Code to Address

郵便番号を XML-RPC(全国郵便番号一覧 | 郵便専門ネットのWEBサービスAPI)を利用して住所に置き換えます。

  • Text • Replace Numbers to X

選択テキスト内にある半角数字を「X」に置き換えます。

  • Text • Search and Replace by Regex

選択テキストの検索と置換を正規表現を利用して行います。

  • Text • Search and Replace

選択テキストの検索と置換を AppleScript の text item delimiter を利用して行います。

  • Text • Separate by Space

テキスト内の半角英字の前後に半角スペースを挿入します。英単語を半角スペースで区切る(CotEditor) -avoidnote- を参考にさせていただきました。

  • Text • Shift Left

選択テキストの行頭にあるタブを一つ削除します。

  • Text • Shift Right

選択テキストの行頭にタブを一つ追加します。

  • Text • Sort

UNIX の sort コマンドを利用して選択テキストをソートします。

  • Text • Trim Empty Lines

空行を削除します。

  • Text • Trim White Space

行頭と行末にある空白(半角スペースとタブ)を削除します。

  • Text • URL Encode

選択テキストを URL エンコードします。

  • Text • View Postal Code in Google Maps

郵便番号から XML-RPC(全国郵便番号一覧 | 郵便専門ネットのWEBサービスAPI)を利用して住所の緯度と経度を取得し、Google Maps で表示します。

  • Text • View Selection Info

選択されているテキストの情報(文字数、行数、単語数)を表示します。

  • TextEdit • View Header File in TextEdit

open コマンドの -h オプションを利用し、Objective-C ヘッダファイル(NSView.h)を TextEdit で表示します。

  • Xcode • View Header File in Xcode

open コマンドの -h オプションを利用し、Objective-C ヘッダファイル(NSView.h)を Xcode で表示します。

以上、全 28 項目。使いやすいように変更、もしくはそのままでご利用ください。

QuickTime Player と AppleScript

Snow Leopard で QuickTime Player が一新されました。どんなものなのかと思っていましたが...、まだまだ QuickTime Player 7 は必要ですね。そんな QuickTime Player ですが、AppleScript の対応度はどれほどのものなのか。ちょっと、調べてみました。

まず、一通りの操作は AppleScript で行えるようです。以前の QuickTime Player は Pro にすると、編集機能や書き出し機能が利用できるようになりました。これらの機能は Pro 版を購入しなくても AppleScript から利用できることはよく知られていました。

Snow Leopard の QuickTime Player は、Pro といったものがなく、最初から全機能を利用できるようになっています。フルスクリーン再生や画面収録、ビデオ収録、オーディオ収録、不要な部分を切り取るトリムなどの機能が最初から利用可能なのですが、以前の QuickTime Player Pro に比べると、かなり貧弱な機能しか利用できません。

例えば、参照ムービーで保存することはできません。トラックを個別に扱うこともできないですし、テキストトラックの追加もできません。「Web 用に保存...」というメニューで保存を行うと参照ムービーが保存されるのですが、これを開くのは QuickTime Player Launcher というアプリケーションで、参照ムービーを開くと自動的に QuickTime Player 7 が起動されるようになっています。つまり、取り扱えないのですね。

ポスターフレームを設定することもできませんし、書き出しといってもあらかじめ設定された書き出ししか行えません。以前のように細かい設定はできないようになっています。どちらかというと不要な機能を取り除き、シンプルになったという方がいいかもしれません。細かい設定を行いたい場合は iMovie 09 か QuickTime Player 7 を使うしかありません。

同じように AppleScript から扱える機能もシンプルです。

document クラスでいえば、ムービーの時間に関連する current time や duration といった属性が秒数を返すようになっています。秒数で結果が返るので、time scale 属性はなくなっています。duration 属性だけで再生時間が取得できるのは手軽でいいのですが、タイムコードを作るときはどうすればいいのでしょう?

System Events を使うと取得することはできますが。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    if not (exists front document) then return

    set the_file to file of front document
    set time_scale to my get_timescale(the_file)
    set selection_start to current time of front document

    set current_time to (selection_start * time_scale) as integer
    set movie_length to ((duration of front document) * time_scale) as integer
    set movie_length to my make_timecode(movie_length, time_scale)
    set timecode_text to my make_timecode(current_time, time_scale)
    set file_name to name of front document
    activate

    display dialog (file_name & return & return & "Length: " & movie_length & return & "Current Time: " & timecode_text) as text with icon 1 buttons {"Clipboard", "OK"} default button 2
    if (button returned of result) is "Clipboard" then
        set the clipboard to timecode_text
    end if
end tell

on make_timecode(current_time, time_scale)
    set total_seconds to current_time div time_scale
    set partial_seconds to current_time mod time_scale
    set h to total_seconds div hours
    set hh to my add_zero(h, 2)
    set m to (total_seconds - (h * hours)) div minutes
    set mm to my add_zero(m, 2)
    set s to (total_seconds - (m * minutes)) div 1
    set ss to my add_zero(s, 2)
    set partial_seconds to my add_zero(partial_seconds, 3)
    set AppleScript's text item delimiters to ":"
    set timecode_text to {hh, mm, ss} as text
    set AppleScript's text item delimiters to {""}
    set timecode_text to timecode_text & "." & partial_seconds
    return ("[" & timecode_text & "]") as text
end make_timecode

on add_zero(int, digit)
    set this_number to int as text
    set n to count this_number
    if n is digit then
        return this_number
    else
        set zero_count to digit - n
        repeat zero_count times
            set this_number to "0" & this_number
        end repeat
        return this_number
    end if
end add_zero

on get_timescale(this_file)
    if (class of this_file) is in {«class furl», file, alias} then
        set this_file to POSIX path of this_file
    end if

    tell application id "com.apple.systemevents"
        tell QuickTime file this_file
            return time scale
        end tell
    end tell
end get_timescale

でも、これって秒数を返すのですからわざわざ time scale を調べなくてもいいような気がします。

選択した範囲内を切り取る trim 命令は秒数で指定するようになっています。しかし、すべてが秒数指定かというとそうではなく、ムービーの再生場所を少しずつ移動させる step forward や step backward 命令はフレーム単位のままです。

step forward や step backward 命令がフレーム単位なら、全体のフレーム数を数えることもできますね。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    if not (front document exists) then return

    tell front document
        set current_time to current time
        set current time to 0
        step forward -- 1 フレーム動かす
        set frame_length to current time
        set current time to current_time

        -- 全体の秒数を 1 フレームの秒数で乗算(総フレーム数)
        set frame_count to duration / frame_length
        -- 1(秒)を 1 フレームの秒数で乗算(fps)
        set fps to 1.0 / frame_length
        set movie_name to name of it
        set msg to name of it & return & return
        set msg to msg & "1 frame seconds: " & (frame_length as text) & return
        set msg to msg & "Frame count: " & (frame_count as text) & return
        set msg to msg & "FPS: " & (fps as text)

        activate
        display dialog msg with icon 1
    end tell
end tell

一応このような計算で総フレーム数や FPS を算出できますが、必ずしも正確ではありません(例えば、画面収録やムービー収録で保存したムービーでは正確な数値を出せない)。正確さを求めるなら、QuickTime Player 7 を使う方がいいです。参考までに QuickTime Player 7 で行った場合を。

Script Editor で開く

tell application id "com.apple.quicktimeplayer"
    if not (front document exists) then return

    tell front document
        set video_track to tracks whose type is "vide"
        set video_track to item 1 of video_track

        set time_scale to time scale
        set movie_length to duration
        set frame_count to count (frames of video_track)

        set num to frame_count * time_scale
        set fps to num / movie_length

        {frame_count, fps}
    end tell
end tell

copy 命令がないのも痛いですね。現在の選択範囲を画像として書き出す...そういうこともできません。そもそも、選択範囲を作り出すことができないですし。当然、ビデオトラックの左右反転や上下反転もできないですし、歪ませることもできません。開かれているすべてのムービーを連結する...そういうこともできません。こうやって見ていると、できないことの方が多いです。できることを並べていった方が早いかな?

では、ムービーのフルスクリーン再生。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    if not (front document exists) then return

    tell front document
        present
        play
    end tell
end tell

present 命令は、以前はいろいろとオプションを指定できましたが、そういう指定はできなくなっています。

次のスクリプトは 10 フレーム先に進むスクリプト。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    if not (front document exists) then return

    tell front document
        if playing then
            stop
        end if

        step forward by 10
    end tell
end tell

step forward を back forward にすれば 10 フレーム戻ります。ムービーの再生速度を変更させるには document クラスの rate 属性を 変更します。2 倍速にするには以下のようにします。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    if not (front document exists) then return

    tell front document
        if playing then
            stop
        else
            set rate to 2.0
        end if
    end tell
end tell

rate 属性にはあり得ない値を設定することもできます。例えば、10000 倍速とか。すごいのは QuickTime Player 7 なら落ちてしまっていた、こういうあり得ない数値でもスムーズに再生し、安定していることです。もっとも、目には何も映りませんが。逆方向に倍速再生したいなら、負の値を rate 属性に設定します。スロー再生したいときは、-1.0 から 1.0 までの値...例えば、0.5 や -0.5 などを設定します。

新しい QuickTime Player は AppleScript から書き出すときでも複数のファイルを並行して書き出すことができます。この辺りが唯一のいいところかもしれません。書き出しの設定は決められたものしか使えませんが。save 命令は同じものを別名で保存するのに利用できます。

書き出しには export 命令を使います。次のスクリプトでは前面のムービーを指定されたフォーマット(複数指定可)で書き出します。

Script Editor で開く

property export_presets : {"Apple TV", "Computer", "iPhone (Cellular)", "iPhone", "iPod", "HD 480p", "HD 720p", "HD 1080p"}
property file_extensions : {".m4v", ".m4v", ".3gp", ".m4v", ".m4v", ".mov", ".mov", ".mov"}

tell application id "com.apple.QuickTimePlayerX"
    activate
    if not (front document exists) then return
    set file_name to name of front document
    set file_path to (file of front document) as text as alias
    set save_folder to my parent_folder(file_path) as text

    set {file_name, ext} to my splitext(file_name)

    set using_presets to choose from list export_presets default items (item 1 of export_presets) with multiple selections allowed
    if using_presets is false then return

    set export_settings to {}
    repeat with i from 1 to count using_presets
        set this_preset to item i of using_presets
        repeat with j from 1 to count export_presets
            if (item j of export_presets) is this_preset then
                set end of export_settings to {item j of export_presets, item j of file_extensions}
            end if
        end repeat
    end repeat

    repeat with this_preset in export_settings
        set {preset_name, ext} to contents of this_preset
        set out_file to save_folder & file_name & " - " & preset_name & ext
        export front document in file out_file using settings preset preset_name
    end repeat
end tell

on splitext(file_name)
    set reversed_name to (reverse of (characters of file_name)) as text
    set num to offset of "." in reversed_name
    if num is 0 then
        set ext to ""
    else
        set reversed_ext to text 1 thru num of reversed_name
        set ext to (reverse of (characters of reversed_ext)) as text
        set ext_num to count ext
        set name_length to count file_name
        set file_name to text 1 thru (name_length - ext_num) of file_name
    end if
    return {file_name, ext}
end splitext

on parent_folder(the_file)
    tell application "Finder" to return container of the_file as alias
end parent_folder

書き出しに利用できるプリセットですが、「コンピュータ」、「Apple TV」、「iPod」、「iPhone」、「iPhone (Cellular)」、「HD 480p」、「HD 720p」、「HD 1080p」だけです。それぞれ、英語で正確に指定します。これらの設定がどこにあるかというと、QuickTime Player のパッケージ内の Resources フォルダにあります。それぞれ、Apple TV.plist といった設定の名前でプロパティリストファイルで保存されています。

このプロパティリストを真似して他の設定を追加することも可能だと思うのですが...。ちょっと、怖くてまだ手を出していません。もし、やってみようと思うなら自己責任でお願いします。

trim 命令は選択範囲を切り取る命令です。秒数で範囲を指定し、その範囲を切り出します。以下のスクリプトはムービーを均一の長さ(ここでは 10 分単位)に分割して保存します。

Script Editor で開く

property split_length : minutes * 10

tell application id "com.apple.QuickTimePlayerX"
    if not (front document exists) then return

    tell front document
        set file_name to name of it
        set file_path to (file of it) as text as alias
        set save_folder to my parent_folder(file_path) as text
        set {file_name, ext} to my splitext(file_name)
        set movie_length to duration

        if movie_length < split_length then return

        set split_count to movie_length div split_length
        set rest_length to movie_length mod split_length

        set length_list to {}
        repeat with i from 1 to (split_count * split_length) by split_length
            set end of length_list to {(i - 1), i + split_length - 1}
        end repeat
        if rest_length is not 0 then set end of length_list to {movie_length - rest_length, movie_length}

        repeat with i from 1 to count length_list
            set {start_time, end_time} to item i of length_list
            set out_file to save_folder & file_name & "_" & (i as text) & ext
            trim from start_time to end_time
            save it in file out_file
            close it saving no
            open file_path
        end repeat
    end tell
end tell

on splitext(file_name)
    set reversed_name to (reverse of (characters of file_name)) as text
    set num to offset of "." in reversed_name
    if num is 0 then
        set ext to ""
    else
        set reversed_ext to text 1 thru num of reversed_name
        set ext to (reverse of (characters of reversed_ext)) as text
        set ext_num to count ext
        set name_length to count file_name
        set file_name to text 1 thru (name_length - ext_num) of file_name
    end if
    return {file_name, ext}
end splitext

on parent_folder(the_file)
    tell application "Finder" to return container of the_file as alias
end parent_folder

0 から 10 分、10 分から 20 分 というようにわざと最初と最後の一秒をかぶらせています。新しいドキュメントを作ってそこにコピーする...といった操作ができないので、オリジナルのファイルから範囲を切り取ったら保存せずに閉じ、またオリジナルのファイルを開く...といったことを繰り返しています。何となく、見栄えが悪いな。

最後に画面収録、オーディオ収録、ビデオ収録について。これらも AppleSciprt から利用することができます。document クラスにはこれらに関する属性、current microphone(音声を録音するのに利用するマイク), current camera(ビデオを録画するのに利用するカメラ), current audio compression(音声の圧縮設定), current movie compression(ビデオの圧縮設定), current screen compression(画面収録の圧縮設定)) があります。

これらの属性は新規に作成した画面収録、オーディオ収録、ビデオ収録、いずれかがないと利用できません。どのような設定や入力源が利用できるかは以下のようにして調べることができます。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    audio recording devices -- 音声入力源一覧
    video recording devices -- ビデオ入力源一覧

    audio compression presets -- 音声収録圧縮設定の一覧
    movie compression presets -- ビデオ収録圧縮設定の一覧
    screen compression presets -- 画面収録圧縮設定の一覧
end tell

これらは application クラスの要素なので、tell document 1 などとしている中では利用できません。また、新規に作成した画面収録、オーディオ収録、ビデオ収録などの属性を実際に変更するには a reference to 演算子を使って参照にしておく必要があります。

新規にオーディオ収録を作成し、オーディオの入力源を変更するには以下のようにします。

Script Editor で開く

tell application id "com.apple.QuickTimePlayerX"
    activate
    set device_names to name of audio recording devices -- 音声入力源一覧
    set using_device to choose from list device_names
    if using_device is false then return

    -- 参照に
    set using_device to a reference to (audio recording device (using_device as text))

    close documents saving no

    set audio_recording to new audio recording
    set current microphone of audio_recording to using_device
end tell

途中で開かれているすべてのムービーを閉じているのは、画面収録、オーディオ収録、ビデオ収録は一度に一つしか作成することができないからです。画面収録をしながらオーディオ収録(またはビデオ収録)ということはできるのですが(QuickTime Player の隠し設定でできるようになったような...)、どのムービーがどの収録を行っているムービーなのかといったことは AppleScript から調べることができません。これも QuickTime Player 7 だと調べることができるのですが。そのため、エラーを回避するためにすべて閉じています。

start 命令で収録の開始が行えます。pause 命令で収録の一時停止ができますが、画面収録では利用することはできません。pause 命令で一時停止している収録は、resume 命令で再会することができます。stop 命令で収録を終了することができます。

これぐらいでしょうか。AppleScript で制御できるのは。QuickTime Player 7 だととてもこの分量でまとめきれないのですが。嬉しいような、悲しいような。

QuickTime Player の AppleScript をまとめたものをおいておきます。基本的にはすべて ~/Library/Scripts/Applications/QuickTime Player に置いて、スクリプトメニューからの利用を想定していますが、使いやすいようにご利用ください。Automator の「AppleScript を実行」アクションを使ってサービスメニューにするのもいいかもしれません。いつものことですが、ご利用はご自身の責任でお願いします。

Automator と AppleScript

Automator で作業をしていると、同じような作業を繰り返していることに気がつきます。定型作業を自動化させるのが Automator の目的なのに、その Automator で同じ作業を繰り返しているなんて。どうも、腑に落ちません。

Automator は登場した当初から AppleScript に対応していました。登場した当初はどうにも重たくて、以来使っていませんでした。Leopard の Automator で「変数」が使えるようになり、「ほう」と思ったものですが、やはりほとんど使いませんでした。

Snow Leopard インストールを期に使ってみたのですが、Automator 自身を Automator で操作できるわけでもなく、Automator の自動化は AppleScript からという環境に変わりはないようです。

唯一、変わったことといえば AppleScript からは「変数」が操作できるようになっていることぐらいでしょうか。反面、新規ワークフローの作成はスムーズにできなくなっています(必ずテンプレートを選択するシートが表示されるので)。

最初にも書いたように Automator は定型作業の繰り返しです。どのワークフローもおおむね同じようなアクションと変数を使います。特に「サービス」関連のワークフローにその傾向があります。

また、デバッグ時に同じようなアクションを何度も追加したり、削除したり...という作業が少なからず発生します。これが、結構手間だったりします。そのためでしょうか、Automator には add と remove という命令が備わっています。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    add Automator action "選択された Finder 項目を取得" to front workflow at index 1
end tell

名前参照で書いていますが、互換性を考えるなら Automator action の bundle id 属性などを使う方がいいと思います。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    set bundle_identifier to "com.apple.Automator.Get_Selected_Finder_Items_2"
    add Automator action id bundle_identifier to front workflow at index 1
end tell

ファイルやフォルダを処理するサービスなどは「選択された Finder 項目を取得」アクションを最初においてワークフローのデバッグを行うのですが、デバッグが終われば必要なくなります。remove 命令を使えば不必要なアクションや変数を取り除くことができます。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    set bundle_identifier to "com.apple.Automator.Get_Selected_Finder_Items_2"

    tell front workflow
        set remove_actions to Automator actions whose bundle id is bundle_identifier
        repeat with this_action in remove_actions
            remove this_action
        end repeat
    end tell
end tell

デバッグ時には特定のアクションの使用、不使用の切り替えを行いたいときがあります。Automator action の enabled 属性で切り替えることができます。次のスクリプトでは使用されているアクションの中から選択されたものを使用停止にします。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    tell front workflow
        set action_list to a reference to (Automator actions whose enabled is true)
        if (count of action_list) is 0 then return

        set action_names to name of action_list
        set use_actions to choose from list action_names default items (item 1 of action_names) with multiple selections allowed
        if use_actions is false then return
        repeat with this_action in use_actions
            set enabled of Automator action this_action to false
        end repeat
    end tell
end tell

変数なども同じように add, remove 命令で追加や削除が行えます。次のスクリプトは変数の一覧の中から選択されたものをワークフローに追加します。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return
    set variable_list to name of variables
    my combSort(variable_list)
    set used_variables to choose from list variable_list default items (item 1 of variable_list) with multiple selections allowed
    if used_variables is false then return

    repeat with this_value in used_variables
        add variable this_value to front workflow
    end repeat
end tell

on combSort(theList)
    set gap to count theList

    repeat
        set gap to (gap * 10) div 13
        if gap is 0 then set gap to 1

        set flag to false
        repeat with i from 1 to (count theList) - gap
            if (item i of theList) is greater than (item (i + gap) of theList) then
                set flag to true
                set tmp to item i of theList
                set item i of theList to item (i + gap) of theList
                set item (i + gap) of theList to tmp
            end if
        end repeat
        if not ((gap is greater than 1) or flag) then exit repeat
    end repeat
end combSort

変数などは複数のワークフローで使い回したいときがあります。テンポラリなファイルの保存先などはそういったものの一つです。あるワークフローにある変数を他のワークフローにコピーする...こういうことも add 命令で可能です。次のスクリプトはワークフローの変数を他のワークフローにコピーするスクリプトです。

Script Editor で開く

tell application "Automator"
    activate
    if (count workflows) < 2 then return

    set name_list to name of workflows

    set source_document to choose from list name_list default items (item 1 of name_list) with prompt "Select source workflow:"
    if source_document is false then return
    set source_document to source_document as text

    set name_list to my remove_item(name_list, source_document)

    set destination_document to choose from list name_list default items (item 1 of name_list) with prompt "Select destination workflow:"
    if destination_document is false then return
    set destination_document to destination_document as text

    set original_variables to name of variables of workflow source_document
    set used_variables to choose from list original_variables default items (item 1 of original_variables) with multiple selections allowed
    if used_variables is false then return
    repeat with this_value in used_variables
        set contents of this_value to variable this_value of workflow source_document
    end repeat

    repeat with this_value in used_variables
        add this_value to workflow destination_document
    end repeat
end tell

on remove_item(the_list, removed_item)
    set new_list to {}

    repeat with this_item in the_list
        set this_item to contents of this_item
        if this_item is not removed_item then
            set end of new_list to this_item
        end if
    end repeat

    return new_list
end remove_item

保存されたワークフローの実態はパッケージなんですが、パッケージの中身は至ってシンプルで document.wflow というファイルがあるだけです(サービスなんかだと Info.plist も追加されるけど)。このファイルはすべての情報が書かれているプロパティリストファイルです。ファイルの中をのぞくだけで、そのワークフローがどんなアクションを利用しているかが分かります。

ダウンロードしてきたワークフローの中身を確認するのに Automator を起動する必要はなく、このファイルを見るとすべてが分かります。このことを利用して、Finder で選択されているワークフローで使われているアクションの一覧を表示するスクリプト。

Script Editor で開く

set workflow_plist to ":Contents:document.wflow"

tell application "Finder"
    set current_selection to selection
    if selection is {} then return

    set info_list to {}
    repeat with this_item in current_selection
        set this_item to this_item as text
        set plist_file to this_item & workflow_plist
        if exists (alias plist_file) then
            set workflow_info to my get_action_info(POSIX path of plist_file)

            if workflow_info is not {} then
                set action_text to my join(workflow_info, return & tab)
                set end of info_list to displayed name of alias this_item & return & tab & action_text & return
            end if
        end if
    end repeat
end tell

if info_list is not {} then
    tell application "TextEdit"
        activate
        set the_document to make new document
        set text of the_document to my join(info_list, return)
    end tell
end if

on join(the_list, delimiter)
    tell (a reference to text item delimiters)
        set {tid, contents} to {contents, delimiter}
        set {the_text, contents} to {the_list as text, tid}
    end tell

    return the_text
end join

on get_action_info(workflow_file)
    tell application "System Events"
        set plist_file to property list file (workflow_file)
        tell plist_file
            set action_info_list to {}
            repeat with this_item in property list items of property list item "actions"
                set end of action_info_list to value of property list item "ActionName" of property list item "action" of this_item
            end repeat
        end tell
        return action_info_list
    end tell
end get_action_info

サービスをいくつか作っていると、メニューに表示される名前を変更したいときがあります。これはワークフローの名前を Finder で変更しても変わるわけではなく、変更するには Automator でワークフローを開き別名で再保存するか、ワークフローのパッケージ内にある Info.plist の値を変更する必要があります。いちいちそういうことをするのも面倒なので、作ったのが次のスクリプト。

Script Editor で開く

property file_extensions : {".workflow"}

tell application "Finder"
    set current_selection to selection
    if current_selection is {} then return

    set workflow_files to {}
    repeat with this_item in current_selection
        set file_name to displayed name of this_item
        set this_item to POSIX path of (this_item as text)
        set {file_name, ext} to my splitext(file_name)
        if ext is in file_extensions then
            set plist_file to this_item & "/Contents/Info.plist"
            my set_defaut_menu_item_name(plist_file, file_name)
        end if
    end repeat
end tell

on set_defaut_menu_item_name(plist_file, document_name)
    tell application "System Events"
        if not (disk item plist_file exists) then return missing value

        tell property list file plist_file
            if not (property list item "NSServices" exists) then return missing value
            tell property list item "NSServices"
                if not (property list item "NSMenuItem" of property list item 1 exists) then return missing value
                tell property list item "NSMenuItem" of property list item 1
                    set menu_text to value of property list item "default"
                    if menu_text is not document_name then
                        set value of property list item "default" to document_name
                    end if
                    return value of property list item "default"
                end tell
            end tell
        end tell
    end tell
end set_defaut_menu_item_name

on splitext(file_name)
    set reversed_name to (reverse of (characters of file_name)) as text
    set num to offset of "." in reversed_name
    if num is 0 then
        set ext to ""
    else
        set reversed_ext to text 1 thru num of reversed_name
        set ext to (reverse of (characters of reversed_ext)) as text
        set ext_num to count ext
        set name_length to count file_name
        set file_name to text 1 thru (name_length - ext_num) of file_name
    end if
    return {file_name, ext}
end splitext

Finder で選択されているワークフローのメニュー名をファイルの名称に変更します。

以上のスクリプト以外にもいくつかまとめて。

いつものことですが、ご利用はご自身の責任でお願いします。

ところで、Automator には AppleScript という変数があります。この変数は変わった変数で、AppleScript の実行結果を変数として利用することができます(「シェルスクリプト」という変数もそうだけど)。変数のレベルで AppleScript やシェルスクリプトが利用でき、かつ、アクションにも同じようなアクションがある...。ここまでくるとやりたい放題ですね。

Automator でサービスを作成する前に...

ようやくのことで Snow Leopard を入れてみました。環境を整備し、ソフトウェアを入れ、じっくりと触ってみました。なんだか、描画が変になることがありますし、微妙な違和感(バグかどうかよくわからない現象が時々起こる)があります。Leopard の方が使いやすかったなぁ...。Leopard は手に馴染んでいた。まぁ、使い勝手は慣れてくるのでしょうが。

さて。

今更の感もありますが、Snow Leopard の目玉の一つ、Automator。以前にも Automator の記事を書きましたが、Snow Leopard になっていろいろと便利になったようなので、Snow Leopard に対応したものを。

Automator は、基本的にはなんらかの「入力」を受け取り、それらを「加工」し、加工したものを「出力」します。この処理プロセスはプログラムの基本なのですが、Automator はプログラムらしさを排除し、わかりやすいインターフェースで処理プロセスを作成することができるアプリケーションです。

Automator が入力として受け取れるデータはいろいろとありますが、基本は「ファイル/フォルダ」か「テキスト」です。出力も同様です。

Automator は入力されたデータを「加工」するのですが、データを加工するのが「アクション」です。アクションは単体で動作するものもありますし、それだけでは動作しないものもあります。アクションを一つ、または複数つなげることで目的のデータに加工し、出力します。アクションは直前のアクションが加工したデータを入力として受け取り、次のアクションに加工したデータを渡します。最後のアクションが出力するデータが最終的な結果となります。

アクションを一つ、または複数並べたものを「ワークフロー」といいます。ワークフローを保存しておくことで様々な場面で繰り返し利用することができます。

Automator を起動すると、最初に「ワークフロー」のテンプレートを選択することになります。利用目的によってこれらのテンプレートから一つを選び、ワークフローの作成を開始します。

テンプレート「ワークフロー」は汎用的なワークフローです。目的に応じてワークフローを作成し、保存しておきます。保存したワークフローの利用方法は多岐に渡ります。

保存されたワークフローを ~/Library/Scripts に入れておくことで、スクリプトメニューから利用することができます。また Terminal からワークフローを指定して automator コマンドで処理をさせることができます。

テンプレート「ワークフロー」で作成したワークフローは「プリントプラグイン」、「イメージキャプチャ・プラグイン」としても利用できます。これらは特別に Automator のテンプレートを利用しなくても特定のフォルダの中に入れておくだけで利用できます。

また、ワークフローを「アプリケーション」として保存しておくとドラッグ & ドロップでデータを受け取ることができるようになります。アプリケーション形式だと「iCal アラーム」としても利用できます。

基本的にはテンプレート「ワークフロー」でワークフローを作成、保存しておくことで上記のような利用が可能になります。しかし、このテンプレートでは「サービス」と「フォルダアクション」として利用できるワークフローにはなりません。「サービス」と「フォルダアクション」だけは少し特殊で、これらはテンプレートを利用しないと作成することはできません。

「フォルダアクション」は最初に対象となるフォルダを設定しておく必要があります。フォルダを設定し、保存(~/Library/Workflows/Applications/Folder Actions に保存される)した時点でフォルダアクションは実行されるようになります。停止は ~/Library/Workflows/Applications/Folder Actions から対象のワークフローを取り除くことによって行います。

「サービス」は ~/Library/Services にワークフローとして保存されるのですが、普通のワークフローではなく、サービス経由でデータを受け取れるように設定されて保存されます。この設定があるため、通常のワークフローは「サービス」として利用することができません。

と、まぁ基本的なところは以上でしょうか。

なんといっても目玉は「サービス」なんでしょうね。ところで、Snow Leopard になってから /Library/Scripts にインストールされるスクリプトが減りましたね。Finder 関連のものは全てなくなってしまいました。また、ColorSync のスクリプトは ColorSyncScripting を利用していたのが sips で代用するようになっていますね。Finder 関連のものがなくなったのは Automator の「サービス」があるからいいでしょ、ということでしょうか。

なくなった Finder 関連のスクリプトの代替サービスは Mac OS X Automation: Services Downloads で入手することができます。ここからダウンロードできるサービスとアクションは全て入れておくといいと思います。便利なものが揃っています。中でも面白いのが「Website Popup」というアクション。URL を渡すと半透明の HUD ウィンドウにウェブサイトを表示してくれます。

今回、久しぶりに Automator を本格的に触ってみました(Mac OS X 10.5 の時は全く使っていませんでした)が...、なかなか楽しませて頂きました。Finder 関連のスクリプトがなくなるのもむべなるかな。

しかし、分かりにくいところもありますが。とてつもなく分かりにくいのが、変数。その次に分かりにくいのが「サービス」の「選択項目」と「検索対象」。両方とも理解できるとどうってことないのですが。

まず、「サービス」の「選択項目」。基本的には「選択されているテキスト」か「選択されているファイルまたはフォルダ」のいずれかになります。さらに「選択されているテキスト」は「URL」、「アドレス」、「電話番号」、「日付」、「メールアドレス」のいずれかから選択することができます。例えば、URL を選んでおくと URL 以外の文字列を選択してもサービスのメニューには表示されません。

サービスはこのように賢く振る舞ってくれるのですが、正確には URL を含んでいる文字列を認識し、それをワークフローの入力として渡します。ですから、入力された文字列は、必ずしも正確な URL だとは限りません。正確な URL を抜き出す作業はユーザーに一任です。この作業を行ってくれるアクションは Mac OS X Automation: Services Downloads からダウンロードできます。Get URLs from Text がそれですが、Get Dates from Text というアクションも一緒にぜひとも入れておきましょう。

「ファイルまたはフォルダ」も同様で、例えば「イメージファイル」を選択しておくと「イメージファイル」以外が選択されているときはメニューに表示されません。

「検索対象」は作成した「サービス」がどのアプリケーションで表示されるかを設定するためのものですが、「すべてのアプリケーション」を選択することでどのアプリケーションであってもメニューに表示されるようになります。問題は「選択項目」でこれを「入力なし」にするとアプリケーションのサービスメニューにしか表示されないようになります。コンテキストメニューの方では表示されません。

つまり「選択項目」を「入力なし」、「検索対象」を「すべてのアプリケーション」にすると、すべてのアプリケーションで利用できるけど、アプリケーションメニューのサービスメニューにしか表示されないサービスになるのです。このようなサービスはいつでもどのような状況からでも利用できるメニューになるのでキーボードショートカットを与えておくと便利に利用できます。

「選択項目」と「検索対象」でどこのメニューに表示されるか、どのようなときに表示されるかが決定されます。

Automator は Mac OS X 10.5 の時から「変数」が使えたのですが...正直、目的の変数をどのように作るのか理解するのには骨が折れました。

「変数」は便利なように最初からデフォルトの値を持ったものがあります。歯車のアイコンがついているものがそれです。他方、値をカスタマイズできるように設定を行える変数があります。V というアイコンがついているものがそれです。

これらの用意さている変数を組み合わせて目的の変数を作成します。では、例えば、ファイルを保存するのに一意の名前を付けたい、といったとき。スクリーンキャプチャしてファイルを保存するとき、「Capture-ユーザー名年月日時間」といった書式のファイル名を作りたいとします。

必要なのは「ユーザー名」と「年月日」と「時間」、それに保存する「場所」です。これらは用意されている変数から作成します。

まず、Automato の「変数」を表示し「ユーザー」を選択、その中にある「ユーザー名(ショートネーム)」をダブルクリックします。

Automator で変数を配置する

すると、ウィンドウの下部にある「変数」に「ユーザー名(ショートネーム)」が追加されます。

変数一覧に追加された変数

同様の手順で「場所」から「ピクチャ」を。そして、「日付と時刻」から「今日の日付」と「現在の時刻」を追加します。これらは値をカスタマイズできる変数です。現在、変数一覧は次のようになっています。

追加された変数一覧

「時刻」と「日付」の書式をカスタマイズします。変数一覧にある「今日の日付」をダブルクリックすると小さなウィンドウが表示されます。「名前」は変数名になります。the_date としておきます。「フォーマット」から「カスタムフォーマット...」を選択し、下図のようにします。

日付のフォーマットを変更

年月日の間にあるハイフンは自分で入力します。また、月日はそれぞれ 2 桁になるように変更しておきます。

月日を 2 桁表示に

同様の手順で「現在の時刻」も下図のように変更しておきます。

時刻のフォーマットを変更

以上で必要な変数は揃いました。後はこれらを組み合わせてファイル名を作成します。変数一覧の部分でコンテキストメニューを表示すると「新規変数...」というメニューが現れます。これが、欲しかった変数で見つけるのに時間がかかった変数です。

コンテキストメニューの「新規変数...」メニュー

この変数は「テキストとデータ」にある「テキスト」という変数なのですが、まさか、これを使って複数の変数を組み合わせるなんて思いもよりませんでした。ともかく、「新規変数...」を選択するとこの変数が追加されます。追加された変数をダブルクリックし、下図のようにします。

変数の変更

分かりにくいのですが「値」は変数一覧にある変数をドラッグ & ドロップして追加します。『ピクチャ』「Capture-」『ユーザー名(ショートネーム)』「_」『the_date』「T」『the_time』という並びになっています(『』で囲んでいるものが変数一覧からドラッグ & ドロップしたもの。「」で囲んでいるものは記述したもの)。

この file_name という変数をワークフローのところにドラッグ & ドロップし、「結果を表示」アクションを使うと変数がどのような結果を返すかが確認できます。

変数の結果を確認

こうやって作った変数はスクリーンキャプチャの保存先や、新規テキストファイルの保存先などとして利用することができます。拡張子は必要ありません。適宜、補ってくれます。

変数を保存先に適用

変数はこうやって使うのですね...。全く知りませんでした。

実際にいくつかサービスを作ってみようと思ったのですが...そこにたどり着くまでが長かった。ということで、次の機会にでも。

Safari のウィンドウとドキュメント

Safari がタブに対応したのはいつでしたでしょうか?タブに対応したのはともかく、AppleScript からタブを操作できるようになったのはいつからでしたでしょうか。こういう時に参考になるのが AS Hole(AppleScriptの穴)。ここを見ると AppleScript でタブがまともに利用できるのは Mac OS X 10.5 以降、Safari 3 以降のようです(Safariの最前面のウィンドウ内のタブを順々に表示)。

何で、今更タブのことなんか?

Safari の AppleScript 対応度というのは Finder などに比べると貧弱なものです。それでも Safari の自動化に何らししょうがないのは AppleScript 内から Safari に JavaScript が実行させることができるからです。

それは、アプリケーションに AppleScript をどのように対応させるか?といった問題へのシンプルな答えでもあります。「ウェブブラウザの自動化や処理がどういう時に必要なのか?」といったことを考えた時、JavaScript を実行できることが何よりも問題の解決に役立ってくれるでしょう。

AppleScript への対応というのは、このようにキモとなる部分さえ外していなければ、Safari の do JavaScript 命令の実装だけで解決してしまうものなのです。これは他のアプリケーションでも同様です。そのため用語辞書を見るとアプリケーション作成者の AppleScript の理解度が分かったりできて面白かったりします。

閑話休題。

ようするに do JavaScript 命令は古くは document オブジェクトを引数に指定していたのですが、タブを操作できるようになると、tab オブジェクトを引数に渡すように変更されたのです。そして、tab オブジェクトは window の要素なのです。

この変更により、古いスクリプトは修正が必要になりました。

また、少し話は変わりますが、Safariで開いているページの一時保存・リストアで紹介されているスクリプト。重宝しています。

重宝しているのだけど、時々エラーになってしまうことがあるようです。どういう時にエラーになるかというと、ダウンロードしている項目を表示する「ダウンロード」というウィンドウや環境設定のウィンドウなどが前面にある時です。要するに document を持たないウィンドウが前面にある時です。

現在の Mac のアプリケーションの多くは Cocoa/Objective-C で作成されています。これらのアプリケーションは多くの window を利用しますが、ドキュメントベースのアプリケーション(書類の編集などができるアプリケーション)は window の中に document というオブジェクトを持っています。細かいことは省きますが、document を持っている window と持っていない window(設定パネルやダイアログ等)の 2 種類があるのです。

AppleScript で document というと、多くの場合この document を持った window のことを指します。例えば、TextEdit などで Book Review.rtf という書類を開いており、最前面に環境設定を開いていたとします。

tell application "TextEdit"
    name of window 1
    --> 環境設定
    name of document 1
    --> "Book Review.rtf"
    name of windows
    --> {"Book Review.rtf", "環境設定"}
end tell

windows として複数の window オブジェクトを取得すると全てのウィンドウが取得できます(document も含む)。そして、上記のスクリプトでは Book Review.rtf を最前面にすると window 1 は Book Review.rtf になります。また、document を持たない環境設定などの window は閉じても AppleScript から取得できます(これらの window の属性 visible が false になるだけで、存在自体が消滅するわけではない)。

これらは AppleScript で window と document を操作する時の注意点ですが...何が問題かというと、Safari の場合、JavaScript を実行させるためにタブを持ったウィンドウを取得しなければならないのだけど、最前面の document が必ずしも最前面の window だとは限らない、ということです。

document を持った window で最前面のものを取得する必要があります。

こういうことを知ってかどうかは分かりませんが、window の属性に document というものがあります。少し前に追加されたものだと思うのですが。また、以前はきちんと動作しなかったような...。この属性を利用して document を持った最前面の window を取得することができます。

Script Editor で開く

tell application "Safari"
    set front_window to my get_front_window()
    if front_window is missing value then return

    tell current tab of front_window
        do JavaScript "new Date();" in it
    end tell
end tell

on get_front_window()
    tell application "Safari"
        if not (front document exists) then return missing value
        set the_document to front document
        set window_ref to a reference to (windows whose document of it is not missing value)
        repeat with this_window in window_ref
            if document of this_window is the_document then return contents of this_window
        end repeat
        return missing value
    end tell
end get_front_window

tab オブジェクトが document の要素だったら話は簡単なんだけど、そうはいかないんでしょうね。この window の document 属性を使って前面の document オブジェクトを正確に指定する...という方法は、他のドキュメントベースのアプリケーションでも利用できるものなのですが、Script Editor(現在では AppleScript Editor)では、用語辞書を表示する window も document を持っている window オブジェクトなので注意が必要だったりします(つまり、document 1 という参照で用語辞書を表示している window を指してしまうこともある)。

Script Editor で開く

tell application id "com.apple.ScriptEditor2"
    name of windows whose document of it is not missing value
    --> {"名称未設定", "StandardAdditions.sdef"}
end tell

document を持っている window はアプリケーションにより異なる...と。System Events を使って GUI Scripting を利用すれば、各アプリケーションで共通して利用できるハンドラも作成できると思うのですが、System Events はどうも処理が遅くて...乗り気になれなかったり(必要があればもちろん使います)。

「iTunes に自動的に追加」と AppleScript

全く知らなかったのですが、iTunes 9 になって iTunes Music フォルダ(現行バージョンでは iTunes Media に名称が変更されている)に「iTunes に自動的に追加(英語環境では Automatically Add to iTunes)」というフォルダが追加されているのですね。このフォルダに iTunes が取り扱えるファイルを放り込んでおくと、自動的に iTunes に取り込んでくれる...そういうフォルダなのだそうです。

どうも、このような機能は待ち望まれていたもののようです。検索を行うと評判はいいようで、関連する記事も散見されます。例えば、ライフハッカー[日本版]の「「iTunes 9」でウォッチフォルダから新曲自動追加ができるようになった : ライフハッカー[日本版]」という記事。

このような「特定のフォルダを監視し、特定のファイルがフォルダに加えられると何らかの処理を行う...」ということをしたい時に、すぐに思いつくのはフォルダアクションです。あまり、利用されていない...のかな、もしかすると。

ライフハッカー[日本版]と同じことをするのは面白くないので、フォルダアクションで同じような機能を実装してみました。

以下のスクリプトを保存しておき、監視しておきたいフォルダにスクリプトを関連づけておくと、iTunes にファイルを自動的に追加することはできます。

Script Editor で開く

property file_extensions : {".mp3"}

on run -- debug
    set file_list to choose file with multiple selections allowed without invisibles
    set the_folder to path to home folder -- dummy
    adding folder items to the_folder after receiving file_list
end run

on adding folder items to this_folder after receiving these_items
    set music_files to {}

    repeat with this_item in these_items
        set {fine_name, ext} to my splitext(this_item as text)
        if ext is in file_extensions then
            set end of music_files to contents of this_item
        end if
    end repeat

    if music_files is not {} then
        set playlist_name to "Added: " & (short date string of (current date))
        tell application "iTunes"
            activate
            set added_tracks to add music_files

            if class of added_tracks is file track then
                set added_tracks to {added_tracks}
            end if

            if not (playlist playlist_name exists) then
                set the_playlist to make new playlist with properties {name:playlist_name}
            else
                set the_playlist to playlist playlist_name
            end if

            repeat with this_track in added_tracks
                duplicate this_track to the_playlist
            end repeat
        end tell
    end if
end adding folder items to

on splitext(file_name)
    set reversed_name to (reverse of (characters of file_name)) as text
    set num to offset of "." in reversed_name
    if num is 0 then
        set ext to ""
    else
        set reversed_ext to text 1 thru num of reversed_name
        set ext to (reverse of (characters of reversed_ext)) as text
        set ext_num to count ext
        set name_length to count file_name
        set file_name to text 1 thru (name_length - ext_num) of file_name
    end if
    return {file_name, ext}
end splitext

フォルダアクションの設定の仕方は...Mac OS X 10.5 と Mac OS X 10.6 では若干異なっているのでしたね。Mac OS X 10.5 の場合は「シゴタノ! —    フォルダアクションと Automator で時間をセーブする」を()、Mac OS X 10.6 の場合は「わかばマークのMacの備忘録 : setWeblocThumb」を参考にしてみてください。

これで監視しているフォルダに MP3 ファイルが追加されたら iTunes に自動的に取り込んでくれます。

しかし、試してみると分かると思うのですが、iTunes の add 命令はファイルの複製なのです。オリジナルのファイルはそのまま残ります。また、フォルダアクションは反応がよくて(よすぎて)、例えば、Safari でダウンロードしているファイルなんかだったりした場合、ダウンロード完了まで待ってくれません。だから、上記のスクリプトでは目的が達成できません(Mac OS X 10.6 のフォルダアクションは項目がコピーされている時は処理を待機するようですが...ダウンロードしている項目にも適用されるのかは分かりません)。

上記のフォルダアクションでもいいのですが、何となく釈然としない部分があります。フォルダの更新を監視するなら、launchd があるな...。launchd と AppleScript の組み合わせでいいじゃないか、と。しかし、この思いつきがハマる原因になるとは。

ハマるなどとはつゆとも思わず、とりあえず作り始めました。最初に書いたように「iTunes に自動的に追加」フォルダの名称は言語環境で異なるようで、英語環境では「Automatically Add to iTunes」になります。この辺りの問題に対応するため、バンドル形式のアプリケーションかスクリプトバンドルで保存し、localized string 命令を利用します。

そして、作成したスクリプトを launchd で実行するプログラムに指定します。プログラムの実行は osascript を利用するのですが...ここでハマる。

osascript でスクリプトファイルを指定して実行すると、どうも言語環境を考慮しないようで localized string 命令が英語環境の文字列を返します。まさか、このような部分に問題があるとも思わず、他の部分をいろいろ調べていました。

もちろん、launchd でアプリケーションを直接起動させてもいいのですが...この場合、アプリケーション内のプログラムの実態そのものを指定する必要があります。AppleScript のバンドル形式のアプリケーションの場合は、パッケージ内の /Contents/MacOS/applet がそれにあたります。が、これを起動させても目的の動作は行われません。他の Cocoa アプリケーションのようにこの方法も利用できない。バンドル形式ではないアプレットで保存すると問題はないのですが、そうなると localized string が利用できません。

osascript で実行すると localized string で問題があり、アプリケーションの指定にも問題がある...。どうしたものかと思いましたが、バンドル形式のアプリケーションで保存し、osascript でアプリケーションを起動させてしまえばいいじゃないかと思いつきました。

具体的な launchd の記述は以下のようになります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.charan.AutomaticallyAddiTunes</string>
    <key>Program</key>
    <string>/usr/bin/osascript</string>
    <key>ProgramArguments</key>
    <array>
        <string>osascript</string>
        <string>-e</string>
        <string>tell application "Move to iTunes" to activate</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Users/your_name/Downloads</string>
    </array>
</dict>
</plist>

これで、かなり満足したものができました。必要なファイルは以下からダウンロードできます。利用方法は同梱の書類を参照してみてください。まぁ、たいていの場合と同じように「ご利用はご自身の責任で」行ってください。

iTunes Music Library.xml を見つけ出す

ちょっと必要があって iTunes Music Library.xml の場所がどこにあるかを AppleScript で探し出したいんだけど...。目的はそれだけなんです。

iTunes Music Library.xml は iMovie や iPhoto、iDVD、Keynote など他のアプリケーションが利用するファイルです。iTunes が管理しているファイルやプレイリストなどの情報が記述されています。拡張子は XML ですが、中身は Apple の Property List 形式で記述されています。

なんで iTunes Music Library.xml を探し出したいかというと、iTunes が利用している iTunes Music フォルダの場所を参照したいからです。その情報が iTunes Music Library.xml に記述されているのです。

通常、iTunes はユーザーの Music フォルダ(~/Music)に iTunes というフォルダを作成し、そこで各ファイルの管理をするのですが、このフォルダの場所はユーザーが変更可能です。ですから、iTunes Music Library.xml が ~/Music/iTunes 以下に存在すると決めつけることはできません。

ところで、iTunes 9 になってファイルの整理方式が変更になったんですね。知りませんでした。うちの iTunes フォルダは古くからのもので、OS のバージョンアップの度に外付け HDD に退避し、バージョンアップ後に戻す...ということを繰り返しているので、現在では利用されていないファイルやフォルダが複数散見されます。このことが余計に事態を複雑にしてくれました。

他のアプリケーションが参照するファイルなんだから、iTunes Music Library.xml の場所を調べる方法が何かあるはず...という想定のもと調べてみると、com.apple.iApps.plist というファイルが見つかりました。ここに iTunes Music Library.xml の場所が記述されているのでした。

ユーザーの Preferences フォルダにある com.apple.iApps.plist は iTunes、もしくは iPhoto の起動時に作成されます。とりあえずこのファイルを見てみれば iTunes Music Library.xml の場所が分かります(まぁ、問題点がないわけでもないようですが)。

以下、iTunes Music Library.xml の場所を調べ、iTunes Music フォルダの場所を調べるスクリプト。エラー処理はしていないので、その辺りは大人の対応で。

Script Editor で開く

set music_folder to find_iTunes_folder()

on find_iTunes_database()
    set prefs_folder to path to preferences folder from user domain as text
    set prefs_file to POSIX path of (prefs_folder & "com.apple.iApps.plist")

    tell application "System Events"
        tell property list file prefs_file
            set file_list to value of property list item "iTunesRecentDatabasePaths"
            return item 1 of file_list
        end tell
    end tell
end find_iTunes_database

on find_iTunes_folder()
    set xml_file to find_iTunes_database()
    tell application "System Events"
        tell property list file xml_file
            set music_folder to value of property list item "Music Folder"
        end tell
    end tell

    return do shell script "python -c \"import sys, urlparse, urllib; print urllib.unquote(urlparse.urlparse(sys.argv[1]).path)\" " & quoted form of music_folder
end find_iTunes_folder