Archive for 12月, 2015

2015/12/30 Popup Buttonを作成

Cocoaの機能を用いてWindowを作成し、その上に2つのポップアップボタン(NSPopUpButton)を配置。指定のArrayでポップアップメニューを作成して、選択結果を取得するAppleScriptです。

but1.png

but2.png

but3.png

たとえばNumbers/Excelスプレッドシート上の2つのセルの値を入れ替えるような処理が必要だったとして、Pure AppleScriptでchoose from listコマンドを2回実行すれば2つのセルを指定できます。ただ、それだと実際の使い勝手はいまいちです。

Xcode上でAppleScriptObjCのプログラムを書けば、完全なGUIを作成できますが、Xcodeで作成したASOCのプログラムはランタイムの挙動が若干(Script Editor上で記述したASOCとは)異なり、また生産性もいまひとつです。

そこで、「ちょっとしたGUI」であれば、AppleScriptのプログラム中で記述したりライブラリから呼び出したりできたほうが便利なケースが多いものと思われます。本Scriptはそうした用途のためのテストプログラムです。

実行時には、Script Editor上ではControl-Command-Rを実行してください。

AppleScript名:ASOCでpopup buttonを作成
– Created 2015-12-30 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

property windisp : false
property wController : false

set ap1List to {“Alpha”, “Bravo”, “Charlie”, “Delta”, “Echo”, “Foxtrot”, “Golf”, “Hotel”, “India”}
set ap2List to {“Juliett”, “Kilo”, “Lima”, “Mike”, “November”, “Oscar”, “Papa”, “Quebec”, “Romeo”}

set aButtonMSG to “OK”
set aSliderValMSG to “Numbers上の値を交換するセルの選択”
set aVal to getPopupValues(ap1List, ap2List, aButtonMSG, aSliderValMSG, 20) of me
–>  {”Alpha”, “Kilo”}–操作した場合
–>  {false, false}–タイムアウト時

on getPopupValues(ap1List, ap2List, aButtonMSG, aSliderValMSG, timeOutSecs)
  
  
set (my windisp) to true
  
  
set aView to current application’s NSView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, 360, 120))
  
  
–Labelをつくる
  
set a1TF to current application’s NSTextField’s alloc()’s initWithFrame:(current application’s NSMakeRect(60, 110, 80, 20))
  
set a2TF to current application’s NSTextField’s alloc()’s initWithFrame:(current application’s NSMakeRect(60, 70, 80, 20))
  
a1TF’s setEditable:false
  
a2TF’s setEditable:false
  
a1TF’s setStringValue:“移動前:”
  
a2TF’s setStringValue:“移動後:”
  
a1TF’s setDrawsBackground:false
  
a2TF’s setDrawsBackground:false
  
a1TF’s setBordered:false
  
a2TF’s setBordered:false
  
  
–Ppopup Buttonをつくる
  
set a1Button to current application’s NSPopUpButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(140, 110, 180, 20)) pullsDown:false
  
set a2Button to current application’s NSPopUpButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(140, 70, 180, 20)) pullsDown:false
  
  
a1Button’s removeAllItems()
  
a2Button’s removeAllItems()
  
  
a1Button’s addItemsWithTitles:ap1List
  
a2Button’s addItemsWithTitles:ap2List
  
  
  
–Buttonをつくる
  
set bButton to (current application’s NSButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(110, 10, 180, 40)))
  
bButton’s setButtonType:(current application’s NSMomentaryLightButton)
  
bButton’s setBezelStyle:(current application’s NSRoundedBezelStyle)
  
bButton’s setTitle:aButtonMSG
  
bButton’s setTarget:me
  
bButton’s setAction:(“clicked:”)
  
bButton’s setKeyEquivalent:(return)
  
  
aView’s addSubview:a1TF
  
aView’s addSubview:a2TF
  
  
aView’s addSubview:a1Button
  
aView’s addSubview:a2Button
  
aView’s addSubview:bButton
  
aView’s setNeedsDisplay:true
  
  
–NSWindowControllerを作ってみた
  
set aWin to (my makeWinWithView(aView, 400, 160, aSliderValMSG))
  
set wController to current application’s NSWindowController’s alloc()
  
wController’s initWithWindow:aWin
  
  
wController’s showWindow:me
  
  
set aCount to timeOutSecs * 10
  
  
set hitF to false
  
repeat aCount times
    if (my windisp) = false then
      set hitF to true
      
exit repeat
    end if
    
delay 0.1
    
set aCount to aCount - 1
  end repeat
  
  
my closeWin:aWin
  
  
if hitF = true then
    set s1Val to a1Button’s titleOfSelectedItem() as string
    
set s2Val to a2Button’s titleOfSelectedItem() as string
  else
    set {s1Val, s2Val} to {false, false}
  end if
  
  
return {s1Val, s2Val}
  
end getPopupValues

on clicked:aSender
  set (my windisp) to false
end clicked:

–make Window for Display
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle)
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
  
set aBacking to current application’s NSTitledWindowMask
  
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
– Window
  
set aWin to current application’s NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
–aWin’s setBackgroundColor:(current application’s NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(current application’s NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
–aWin’s makeKeyAndOrderFront:(me)
  
  
aWin’s setContentView:aView
  
  
return aWin
  
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

★Click Here to Open This Script 

2015/12/27 スライダー+ボタンを動的に生成 v3

Cocoaの機能を用いて、NSWindow+NSSplitViewをプログラマティックに動的に作成し、その中にNSSliderとNSButtonを入れてSliderの値を取得するAppleScriptです。

従来のバージョンではボタンにキーボードショートカットを設定できていなかったため、キーボードショートカット(returnキー)を設定してみました。

nswindow1.png

これだけでも、ずいぶんと有用性が増します。

AppleScript名:ASOCでslider+buttonを作成 v3
– Created 2015-12-27 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

property windisp : false
property wController : false
property aSliderValMSG : “”

set aMaxVal to 10
set aButtonMSG to “OK”
set aSliderValMSG to “スライダーの設定値:”
set aVal to getSliderValue(aMaxVal, aButtonMSG, aSliderValMSG) of me

on getSliderValue(aMaxVal, aButtonMSG, aSliderValMSG)
  set (my windisp) to true
  
set (my aSliderValMSG) to aSliderValMSG
  
  
set aView to current application’s NSSplitView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, 360, 40))
  
aView’s setVertical:false
  
  
–Sliderをつくる
  
set aSlider to makeSider(aMaxVal) of me
  
  
–Buttonをつくる
  
set bButton to (current application’s NSButton’s alloc()’s init())
  
bButton’s setTitle:aButtonMSG
  
bButton’s setTarget:me
  
bButton’s setAction:(“clicked:”)
  
  
bButton’s setKeyEquivalent:(return) –キーボードショートカット(リターンキー)
  
  
aView’s addSubview:aSlider
  
aView’s addSubview:bButton
  
aView’s setNeedsDisplay:true
  
  
–NSWindowControllerを作ってみた
  
set aWin to (my makeWinWithView(aView, 400, 80, aSliderValMSG & (aMaxVal div 2) as string))
  
set wController to current application’s NSWindowController’s alloc()
  
wController’s initWithWindow:aWin
  
  
set aCount to 1800
  
repeat aCount times
    if (my windisp) = false then
      exit repeat
    end if
    
delay 0.1
    
set aCount to aCount - 1
  end repeat
  
  
my closeWin:aWin
  
  
set sVal to aSlider’s intValue()
  
return sVal
end getSliderValue

on sliderChanged:aSender
  set aVal to aSender’s intValue()
  
set parentWin to aSender’s |window|()
  
parentWin’s setTitle:(my aSliderValMSG & (aVal as text))
end sliderChanged:

on clicked:aSender
  set (my windisp) to false
end clicked:

–make Window for Display
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle)
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
  
set aBacking to current application’s NSTitledWindowMask
  
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
– Window
  
set aWin to current application’s NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
–aWin’s setBackgroundColor:(current application’s NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(current application’s NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
aWin’s makeKeyAndOrderFront:(me)
  
  
aWin’s setContentView:aView
  
  
return aWin
  
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

on makeSider(aMaxNum)
  set aSlider to current application’s NSSlider’s alloc()’s init()
  
aSlider’s setMaxValue:aMaxNum
  
aSlider’s setMinValue:1
  
aSlider’s setNumberOfTickMarks:aMaxNum
  
aSlider’s setKnobThickness:50
  
aSlider’s setAllowsTickMarkValuesOnly:true
  
aSlider’s setTickMarkPosition:(current application’s NSTickMarkBelow)
  
aSlider’s setIntValue:(aMaxNum div 2)
  
aSlider’s setTarget:me
  
aSlider’s setAction:(“sliderChanged:”)
  
return aSlider
end makeSider

★Click Here to Open This Script 

2015/12/24 LAN内のAFPホスト名をリストアップ v2

Cocoaの機能を用いて、LAN内のAFPベースのファイル共有を行っているホスト名の一覧を取得するAppleScriptです。

最初のバージョンでは、

 AppleScriptを実行したマシン自身のホスト名も返ってくる
 同じホスト名が複数回返ってくるケースがある

といった問題が見られました。また、

 I塒廚淵ぅ戰鵐肇魯鵐疋蕕含まれていた

というテスト段階のScriptならではの盲腸もあったため(近年、盲腸の人体内における役割が科学的に解明されつつありますが、あくまで慣用句的な意味において)、これらを改修して動作に支障がないことを確認しました。

AFP(AppleTalk over TCP/IPによるファイル共有)がオンになっている(≒ファイル共有がオンになっている)LAN上のホスト名を返します。検証範囲はOS Xマシンだけで、iOSデバイスについてはテスト対象としていません。

AFP以外のネットワークサービスを検出するテストを行っていますが、現状の待ち時間&Wait Loopの回数だと「LAN内にサービスが存在しない」場合にはかなり待たされるため、適宜変更したほうがよいかもしれません(ループ回数にとくに根拠はないので)。

AppleScript名:ASOCでafpホスト名をリストアップする v2
– Created 2015-12-24 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property foundHosts : {}
property myHostName : “”

findHostsViaBonjour(“_afpovertcp._tcp”) of me –AFP’s port:548
–> {”MBA13″, “Mini_2″, “MBA11″}

on findHostsViaBonjour(aType)
  
  
set foundHosts to {}
  
set myHostName to (current application’s NSHost’s currentHost()’s localizedName()) as string
  
  
set aBrowser to current application’s NSNetServiceBrowser’s alloc()’s init()
  
aBrowser’s setDelegate:me
  
  
aBrowser’s searchForServicesOfType:aType inDomain:“”
  
  
repeat 1000 times
    if foundHosts is not equal to {} then exit repeat
    
delay 0.01
  end repeat
  
  
aBrowser’s setDelegate:(missing value)
  
  
return foundHosts
  
end findHostsViaBonjour

on netServiceBrowser:aNetServiceBrowser didFindService:aNetService moreComing:aMoreComing
  set aDesc to aNetServiceBrowser’s |description|()
  
set bDesc to aNetService’s |description|()
  
set cDesc to (aNetService’s |name|()) as text
  
  
if cDesc is not in foundHosts and cDesc is not equal to myHostName then
    set the end of foundHosts to cDesc
  end if
end netServiceBrowser:didFindService:moreComing:

★Click Here to Open This Script 

2015/12/23 LAN内のAFPホスト名をリストアップ

Cocoaの機能を用いて、LAN内のAFPベースのファイル共有を行っているホスト名の一覧を取得するAppleScriptです。

toplevel.png

Finder上の「ネットワーク」を選択すると、ファイル共有でアクセス可能なマシンの一覧が表示されます。

afp_hosts.png

これと同様に、(GUI表示は行いませんが)AFPをオンにしている(ファイル共有をオンにしている)ホスト名の一覧リストを取得します。AppleScriptを実行しているマシン自体でもファイル共有をオンにしていると、一緒にリストアップされます(自分自身は除外しておいたほうがいいかも)。

ただ、まだぜんぜんテスト段階のScriptなので問題が出るかもしれません(同じホスト名が複数回出現したりするので、そのあたり除外すべき)。

AFPホスト名の一覧を取得するのにさほど時間はかかっていません。キャッシュした結果を使っているか、ルーター(AirMac/AirPort Base Station)側で保持している値を返しているだけにも見えます(スリープ中のマシンの名称も見えています)。

取得した名称のホストにAFPで接続できる状態かどうかは、実際に別途確認しておいたほうがよいように見えます。

AppleScript名:ASOCでホスト名をリストアップする
– Created 2015-12-23 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property foundHosts : {}

findHostsViaBonjour(“_afpovertcp._tcp”) of me –AFP’s port:548
–>  {”MBA11″, “MBA13″, “Mini_2″, “MBPR”, “MBA11_2″}

on findHostsViaBonjour(aType)
  
  
set foundHosts to {}
  
  
set aBrowser to current application’s NSNetServiceBrowser’s alloc()’s init()
  
aBrowser’s setDelegate:me
  
  
aBrowser’s searchForServicesOfType:aType inDomain:“”
  
  
repeat 1000 times
    if foundHosts is not equal to {} then exit repeat
    
delay 0.01
  end repeat
  
  
aBrowser’s setDelegate:(missing value)
  
  
return foundHosts
  
end findHostsViaBonjour

on netServiceBrowser:aNetServiceBrowser didFindService:aNetService moreComing:aMoreComing
  set aDesc to aNetServiceBrowser’s |description|()
  
set bDesc to aNetService’s |description|()
  
set cDesc to aNetService’s |name|()
  
set the end of foundHosts to (cDesc as text)
end netServiceBrowser:didFindService:moreComing:

on netServiceDidResolveAddress:aSender
  log aSender’s |description|()
  
log “netServiceDidResolveAddress”
end netServiceDidResolveAddress:

★Click Here to Open This Script 

2015/12/22 2フォルダ間のファイル内容比較

Cocoaの機能を用いて、2つのフォルダ内のファイルの内容比較を行うAppleScriptです。

folders.png

複数バージョンのAppleScriptで、処理結果がきちんと同じかどうか比較したいケースがあります。そこで手作業で比較・・・していたのでは意味がありません。

たまたま仕事でCSVを書き出すAppleScriptをバージョンアップしていて、前バージョンの処理結果ととくに変化がないことを検証するために、

 。殴侫ルダ間のファイル名一覧に変更がないこと
 同一ファイル名のファイルで内容が同一であること

をチェックする必要がありました。Pure AppleScriptで書こうとすると、けっこうめんどくさい処理です(書いたことがあるような気がしますが、書き捨てしてしまったのかも)。

Cocoaの機能を使って書くとどうなるのか? 調べてみてあまりに簡潔に書けるので、「本当にこれでいいの?」と、わざと2フォルダ間でファイル数を変えてみたり、同名でまったく内容の違うファイルを入れてみたりしましたが、きちんと検出してくれました。

2フォルダにそれぞれ1050個のCSVファイルを入れた状態で、MacBook Pro Retina 2012で(SSD上で)実行すると比較に1秒かかりません。HDD上で比較を行うとそれなりに時間はかかりますが、Mac mini 2014(Core i5 2.6GHz)上で3秒程度でした。

AppleScript名:2フォルダ間の内容比較
– Created 2015-12-22 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aFol to (choose folder with prompt "Select Folder A")
set bFol to (choose folder with prompt "Select Folder B")

set aPath to POSIX path of aFol
set bPath to POSIX path of bFol

set aFM to current application’s NSFileManager’s defaultManager()
set aRes to (aFM’s contentsEqualAtPath:aPath andPath:bPath) as boolean

★Click Here to Open This Script 

2015/12/21 指定オブジェクトを可視化して返す

Cocoaの機能を用いて、指定Cocoaオブジェクトを可視化して返すAppleScriptです。

ASObjC Explorer 4は素晴らしいエディタで、Cocoaオブジェクトをログ出力して、

asobjcex4.png
▲ASObjC Explorer 4上でCocoaオブジェクトをログ表示させたところ

のように目で見て確認できます。ただ、OS標準装備のScript Editorではこれが何も確認できないため、Script EditorとASObjC Explorer 4の機能差を比較すると「0対100」ということに。

cocoaobjse.png
▲Script Editor上でCocoaオブジェクトをログ表示させたところ

これが20や30対100であれば、もうちょっとASObjC Explorer 4への興味を喚起できると思うのですが、もともとのScript Editor側の機能が0だとなかなか誘導もできません。

そこで、いろいろScript Editor上でCocoaオブジェクトを可視化する手段を試行錯誤してきましたが、

「とりあえず、データをplistに書き込んで、文字列として読み込むのが簡単でよいのでは?」

と、思いついて試してみたものです。

cocoaobject.png
▲本Scriptを用いてScript Editor上でCocoaオブジェクトをログ表示させたところ

さて、どうなりますやら、、、、

AppleScript名:ASOCで指定オブジェクトを可視化して返す
– Created 2015-12-21 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aData to current application’s NSMutableArray’s arrayWithArray:{{abc:123, cde:“abcdef”}, {abc:234, cde:“bcdefg”}}
set aRes to getVisualizedDataString(aData)

–与えられたデータを可視化してテキストで返す
on getVisualizedDataString(aData)
  set dtPath to POSIX path of (path to temporary items folder)
  
set outPath to dtPath & (current application’s NSUUID’s UUID()’s UUIDString() as text) & “.plist”
  
aData’s writeToFile:outPath atomically:true
  
  
set aText to (current application’s NSString’s stringWithContentsOfFile:outPath encoding:(current application’s NSUTF8StringEncoding) |error|:(missing value)) as text
  
set defM to current application’s NSFileManager’s defaultManager
  
defM’s removeItemAtURL:(current application’s |NSURL|’s fileURLWithPath:outPath) |error|:(missing value)
  
  
return aText
end getVisualizedDataString

★Click Here to Open This Script 

2015/12/18 指定言語のスペルチェックを行い、候補文を返す

Cocoaの機能を用いて、NSSpellCheckerで指定言語(Englishとか)でスペルチェックを行い、スペルミスがあれば候補文を返すAppleScriptです。

日本語(Japanese)はスペルチェック対象の言語として指定できませんが、よくよく考えると日本語はキーボードからのかな漢字変換入力時にスペルチェックされているようなはずのものなので、本来はスペルチェックよりも入力時の水際チェックに力を入れるべきものなのかもしれません。

日本語はスペルミスを簡単には検出しにくい言語なので、仕方のないところでしょう。

AppleScript名:指定言語のスペルチェックを行い、候補文を返す
– Created 2015-12-18 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

set aText to “This is a penz.”
set spRes to spellCheckingInSpecifiedLanguage(aText, “English”) of me
–>  {missed:”penz”, suggestionList:{”This Is A Pens”, “This Is A Pend”, “This Is A Penh”, “This Is A Penn”, “This Is A Pent”, “This is a pen”}}

set aText to “Applele”
set spRes to spellCheckingInSpecifiedLanguage(aText, “English”) of me
–> {missed:”Applele”, suggestionList:{”Apple”, “Apples”, “Appeal”, “Appalled”, “Appellate”}}

set aText to “Applele”
set spRes to spellCheckingInSpecifiedLanguage(aText, “French”) of me
–>  {missed:”Applele”, suggestionList:{”Appelle”, “Appelée”, “Appelles”}}

set spRes to spellCheckingInSpecifiedLanguage(aText, “fr”) of me
–>  {missed:”Applele”, suggestionList:{”Appelle”, “Appelée”, “Appelles”}}

on spellCheckingInSpecifiedLanguage(aText, aLang)

  set aStr to current application’s NSString’s stringWithString:aText
  
set aChecker to current application’s NSSpellChecker’s sharedSpellChecker()
  
aChecker’s setLanguage:aLang
  
  set aMisspelledRange to aChecker’s checkSpellingOfString:aStr startingAt:0
  
if aMisspelledRange’s |length|() = 0 then return missing value
  
  –Get Misspelled String  
  
set aMisString to (aStr’s substringWithRange:aMisspelledRange) as text
  
  –Get Suggestion
  
set aSugList to (aChecker’s guessesForWord:aStr) as list
  
–>  {”This Is A Pens”, “This Is A Pend”, “This Is A Penh”, “This Is A Penn”, “This Is A Pent”, “This is a pen”}
  
  return {missed:aMisString, suggestionList:aSugList}
end spellCheckingInSpecifiedLanguage

★Click Here to Open This Script 

2015/12/18 NSSpellCheckerでスペルチェック可能な言語の一覧リストを取得

Cocoaの機能を用いて、NSSpellCheckerでスペルチェック可能な言語名称の一覧リストを取得するAppleScriptです。

AppleScript名:NSSpellCheckerでスペルチェック可能な言語の一覧リストを取得
– Created 2015-12-18 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set aChecker to current application’s NSSpellChecker’s sharedSpellChecker()
set langList to (aChecker’s availableLanguages()) as list
–>  {"en", "en_GB", "en_AU", "en_CA", "en_IN", "en_SG", "fr", "da", "de", "es", "it", "nl", "nb", "pl", "pt_BR", "pt_PT", "fi", "sv", "tr", "ru", "ko"}

set langNameList to {}
repeat with i in langList
  set j to contents of i
  
set aLocale to (current application’s NSLocale’s localeWithLocaleIdentifier:j)
  
set aLocName to (aLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:j)
  
set the end of langNameList to aLocName as string
end repeat

return langNameList
–>  {"English", "English (United Kingdom)", "English (Australia)", "English (Canada)", "English (India)", "English (Singapore)", "français", "dansk", "Deutsch", "español", "italiano", "Nederlands", "norsk bokmål", "polski", "português (Brasil)", "português (Portugal)", "suomi", "svenska", "Türkçe", "русский", "한국어"}

★Click Here to Open This Script 

2015/12/18 英文のスペルチェックを行い、候補文を返す

Cocoaの機能を用いて、英文のスペルチェックを行い、スペルミスがあった場合には候補文を返すAppleScriptです。

何も言語を指定していないので英語になっていますが、当然のように他の言語を指定したくなります。ただ、日本語は無理でしょうね、、、

日本語のスペルチェックは無理だとしても、制限したキーワードを逸脱しないかどうか(AppleScriptの命令の予約語程度とか)を、独自のキーワード辞書を与えてスペルチェックするぐらいはしたいところです。

AppleScript名:英文のスペルチェックを行い、候補文を返す
– Created 2015-12-18 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

set aText to “This is a penz.”
set spRes to spellChecking(aText) of me
–>  {missed:”penz”, suggestionList:{”This Is A Pens”, “This Is A Pend”, “This Is A Penh”, “This Is A Penn”, “This Is A Pent”, “This is a pen”}}

set aText to “AppleScript”
set spRes to spellChecking(aText) of me
–>  missing value

set aText to “Applele”
set spRes to spellChecking(aText) of me
–> {missed:”Applele”, suggestionList:{”Apple”, “Apples”, “Appeal”, “Appalled”, “Appellate”}}

on spellChecking(aText)
  set aStr to current application’s NSString’s stringWithString:aText
  
set aChecker to current application’s NSSpellChecker’s sharedSpellChecker()
  
set aMisspelledRange to aChecker’s checkSpellingOfString:aStr startingAt:0
  
if aMisspelledRange’s |length|() = 0 then return missing value
  
  
–Get Misspelled String  
  
set aMisString to (aStr’s substringWithRange:aMisspelledRange) as text
  
  
–Get Suggestion
  
set aSugList to (aChecker’s guessesForWord:aStr) as list
  
–>  {”This Is A Pens”, “This Is A Pend”, “This Is A Penh”, “This Is A Penn”, “This Is A Pent”, “This is a pen”}
  
  
return {missed:aMisString, suggestionList:aSugList}
end spellChecking

★Click Here to Open This Script 

2015/12/16 ASOCでインターネット接続確認

Cocoaの機能を用いたインターネット接続確認を行うAppleScriptです。

やり方はAppleScriptでもObjective-Cでも変わらないというか、AppleScriptでもはるかかなた昔から行われているやり方で使う道具が違うだけとか、そういう感じです。

AppleScript名:ASOCでインターネット接続確認
– Created 2015-12-16 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aRes to hasInternetConnection() of me
–>  true

on hasInternetConnection()
  set aURL to current application’s |NSURL|’s alloc()’s initWithString:“http://www.google.com”
  
set aReq to current application’s NSURLRequest’s alloc()’s initWithURL:aURL cachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:5.0
  
set urlRes to (current application’s NSURLConnection’s sendSynchronousRequest:aReq returningResponse:(missing value) |error|:(missing value))
  
if urlRes = missing value then
    return false
  else
    return true
  end if
end hasInternetConnection

★Click Here to Open This Script 

2015/12/13 Code39のバーコードのPDFを生成

オープンソースのフレームワーク「BarcodeKit」を利用して、デスクトップに指定のデータのCode3 of 9のバーコードイメージをPDFで出力するAppleScriptです。

本Scriptを動かすためには、GithubからBarcodeKitのソースをダウンロードしてXcode上でビルドしてフレームワークを生成して、~/Library/Frameworksフォルダに入れておく必要があります。ただし、出来上がったバーコードを印刷してバーコードスキャナで読み込むと元のデータのとおりになっていないので、まだ利用はおすすめしません。

もともと、BarcodeKitはビルドするとフレームワークを生成するため、AppleScriptから機能を利用しやすいはずのものですが・・・サンプルコードがあまり存在しないため、いまひとつピンと来ない感じがしています。

とりあえず、Code 3 of 9のバーコードを生成し、

bcd1.png

Keynoteの書類にはりつけて印刷し、

bcd2.png

USB接続のバーコードリーダーで読んでみると・・・

img_3215a.jpg

もともとのデータでは「0123456789」を指定してあったものが、読み込むと「0123446889」といったデータに(汗)

bc_compare.png

まだ、うまく行っていない部分があるのか? ちょっとわからないですが、BarcodeKitフレームワークに問題がある可能性も考えられます。ねんのために、Code 3 of 9がそのまま印刷できるフォント「BC39」でデータを指定して(前後に「*」を入れて)みたものも用意しましたが、そちらは印刷したものをスキャンすると正しく元データに復元できました(つまり、バーコードリーダーの故障ではありません)。

AppleScript名:Code39のバーコードを生成
– Created 2015-12-13 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “BarcodeKitOSX”
use framework “AppKit”

set dtPath to POSIX path of (path to desktop folder)
set outPath to dtPath & (current application’s NSUUID’s UUID()’s UUIDString() as text) & “.pdf”
set aBK to current application’s BKCode39Barcode’s alloc()’s initWithData:(current application’s NSData’s dataWithData:“0123456789″)
aBK’s quietZoneWidth() –> 0

set aView to current application’s BKBarcodeView’s alloc()’s init()
aView’s setBarcode:aBK

set aBounds to aView’s |bounds|()
set aData to aView’s dataWithPDFInsideRect:(aBounds)
aData’s writeToFile:outPath atomically:true

★Click Here to Open This Script 

2015/12/11 テキストビュー+ボタンを動的に生成

Cocoaの機能を用いて、NSSplitViewを作成して、その中にNSTextViewとNSButtonを入れてテキストを表示するAppleScriptです。

NSTextViewはNSScrollViewに入れているので、上下にスクロールします。ウィンドウの表示タイムアウト時間をパラメータで与えるようにしているので、タイムアウトに180秒以外の時間も指定可能です。ボタンをクリックするとウィンドウ表示を中止します。

textv.png

AppleScriptの処理結果を表示したり、ログ表示を行うような用途を想定した部品です。

mkmapv.png

ほかに、地図も表示できてはいるものの、現在位置だけで「任意の場所」を表示するところまでは行っていません。

slider.png

Progress Indicatorを表示させると、やや実用性があるかもしれません。

AppleScript名:ASOCでテキストビュー+ボタンを作成
– Created 2015-12-11 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

property windisp : false

set aWidth to 450
set aHeight to 400

set aTitle to “テキストビューのじっけん/TextView Test”
set outText to “ぴよまるソフトウェア / Piyomaru Software “
set aButtonMSG to “OK”

–表示用テキストの作成
set dispStr to current application’s NSMutableString’s alloc()’s init()
repeat with i from 1 to 100
  set bStr to (current application’s NSMutableString’s stringWithString:(outText & (i as string) & return))
  
set dispStr to (bStr’s stringByAppendingString:dispStr)
end repeat

dispTextView(aWidth, aHeight, aTitle, dispStr, aButtonMSG, 180) of me

on dispTextView(aWidth as integer, aHeight as integer, aTitle as text, dispStr, aButtonMSG as text, timeOutSecs as number)
  
  
set aColor to current application’s NSColor’s colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0
  
set (my windisp) to true
  
  
–Text View+Scroll Viewをつくる
  
set aScroll to current application’s NSScrollView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight))
  
set aView to current application’s NSTextView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight))
  
aView’s setRichText:true
  
aView’s useAllLigatures:true
  
aView’s setTextColor:(current application’s NSColor’s yellowColor()) –cyanColor
  
aView’s setBackgroundColor:aColor
  
aScroll’s setDocumentView:aView
  
aView’s enclosingScrollView()’s setHasVerticalScroller:true
  
  
–Buttonをつくる
  
set bButton to (current application’s NSButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, 40)))
  
bButton’s setTitle:aButtonMSG
  
bButton’s setTarget:me
  
bButton’s setAction:(“clicked:”)
  
  
–SplitViewをつくる
  
set aSplitV to current application’s NSSplitView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aHeight, aWidth))
  
aSplitV’s setVertical:false
  
  
aSplitV’s addSubview:aScroll
  
aSplitV’s addSubview:bButton
  
aSplitV’s setNeedsDisplay:true
  
  
set aWin to makeWinWithView(aSplitV, aWidth, aHeight, aTitle, 0.9)
  
aWin’s makeKeyAndOrderFront:(missing value)
  
aWin’s makeFirstResponder:aView
  
aView’s setString:dispStr
  
  
set aCount to timeOutSecs * 10 –timeout seconds * 10
  
repeat aCount times
    if (my windisp) = false then
      exit repeat
    end if
    
delay 0.1
    
set aCount to aCount - 1
  end repeat
  
  
my closeWin:aWin
  
end dispTextView

–Button Clicked Event Handler
on clicked:aSender
  set (my windisp) to false
end clicked:

–make Window for Input
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle, alphaV)
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
set aBacking to current application’s NSTitledWindowMask –NSBorderlessWindowMask
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
– Window
  
set aWin to current application’s NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
–aWin’s setBackgroundColor:(current application’s NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(current application’s NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setAlphaValue:alphaV –append
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
aWin’s makeKeyAndOrderFront:(me)
  
  
– Set Custom View
  
aWin’s setContentView:aView
  
  
return aWin
  
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

★Click Here to Open This Script 

2015/12/10 スライダー+ボタンを動的に生成

Cocoaの機能を用いて、NSSplitViewを作成して、その中にNSSliderとNSButtonを入れてSliderの値を取得するAppleScriptです。

動的にWindow+Split Viewを作成して、SliderとButtonも作成。SliderについてはWindowのタイトル部分に値を表示させ、ボタンを押すとウィンドウを閉じてSliderの値を取得します。

splitv.png

フルセットのWindowを作成するほどではないが、ユーザーにSliderで値を設定してほしいような場合を想定しています。

AppleScript名:ASOCでslider+buttonを作成 v2
– Created 2015-12-10 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

property windisp : false
property aSliderValMSG : ""

set aMaxVal to 10
set aButtonMSG to "OK"
set aSliderValMSG to "スライダーの設定値:"
set aVal to getSliderValue(aMaxVal, aButtonMSG, aSliderValMSG) of me

on getSliderValue(aMaxVal, aButtonMSG, aSliderValMSG)
  set (my windisp) to true
  
set (my aSliderValMSG) to aSliderValMSG
  
  
set aView to current application’s NSSplitView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, 360, 40))
  
aView’s setVertical:false
  
  
–Sliderをつくる
  
set aSlider to makeSider(aMaxVal) of me
  
  
–Buttonをつくる
  
set bButton to (current application’s NSButton’s alloc()’s init())
  
bButton’s setTitle:aButtonMSG
  
bButton’s setTarget:me
  
bButton’s setAction:("clicked:")
  
  
aView’s addSubview:aSlider
  
aView’s addSubview:bButton
  
aView’s setNeedsDisplay:true
  
  
set aWin to (my makeWinWithView(aView, 400, 80, aSliderValMSG & (aMaxVal div 2) as string))
  
  
set aCount to 1800
  
repeat aCount times
    if (my windisp) = false then
      exit repeat
    end if
    
delay 0.1
    
set aCount to aCount - 1
  end repeat
  
  
my closeWin:aWin
  
  
set sVal to aSlider’s intValue()
  
return sVal
end getSliderValue

on sliderChanged:aSender
  set aVal to aSender’s intValue()
  
set parentWin to aSender’s |window|()
  
parentWin’s setTitle:(my aSliderValMSG & (aVal as text))
end sliderChanged:

on clicked:aSender
  set (my windisp) to false
end clicked:

–make Window for Display
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle)
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
  
set aBacking to current application’s NSTitledWindowMask
  
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
– Window
  
set aWin to current application’s NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
–aWin’s setBackgroundColor:(current application’s NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(current application’s NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
aWin’s makeKeyAndOrderFront:(me)
  
  
aWin’s setContentView:aView
  
  
return aWin
  
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

on makeSider(aMaxNum)
  set aSlider to current application’s NSSlider’s alloc()’s init()
  
aSlider’s setMaxValue:aMaxNum
  
aSlider’s setMinValue:1
  
aSlider’s setNumberOfTickMarks:aMaxNum
  
aSlider’s setKnobThickness:50
  
aSlider’s setAllowsTickMarkValuesOnly:true
  
aSlider’s setTickMarkPosition:(current application’s NSTickMarkBelow)
  
aSlider’s setIntValue:(aMaxNum div 2)
  
aSlider’s setTarget:me
  
aSlider’s setAction:("sliderChanged:")
  
return aSlider
end makeSider

★Click Here to Open This Script 

2015/12/09 ボタンを動的に作成

Cocoaの機能を用いて、ボタンを動的に生成するAppleScriptです。

何らかのデータを試験的に表示するためにビュー1つだけを生成してWindow表示することはやってきましたが、ボタンのような部品はやっていませんでした。最終的には、ビューとボタンを生成して、一緒のウィンドウに収められると有用性が高まることでしょう(まだできていない)。

本Scriptではボタンを生成して、ビューも作らずにいきなりWindowのsubviewに指定しています(乱暴な、、、)。

button1.png
▲カウントダウンを実施(ウィンドウがほぼすべてボタン)

button2.png
▲ボタンをクリックしたところ

AppleScript名:ASOCでボタンを作成
– Created 2015-12-09 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

property windisp : false

set (my windisp) to true

set aButton to current application’s NSButton’s alloc()’s init()
aButton’s setTitle:“TEST”
aButton’s setButtonType:(current application’s NSMomentaryLightButton)
aButton’s setBezelStyle:(current application’s NSSmallSquareBezelStyle)

aButton’s setTarget:me
aButton’s setAction:(“clicked:”)

set aWin to (my makeWinWithView(aButton, 400, 100, “Button Test”))

set aCount to 180
repeat aCount times
  aButton’s setTitle:((aCount as text) & ” seconds left…”)
  
if (my windisp) = false then
    exit repeat
  end if
  
delay 1
  
set aCount to aCount - 1
end repeat
my closeWin:aWin

on clicked:aSender
  tell current application
    display dialog “Clicked”
  end tell
  
set (my windisp) to false
end clicked:

–make Window for Display
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle)
  set aScreen to current application’s NSScreen’s mainScreen()
  
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
  
  
set aBacking to current application’s NSTitledWindowMask
  
  
set aDefer to current application’s NSBackingStoreBuffered
  
  
– Window
  
set aWin to current application’s NSWindow’s alloc()
  (
aWin’s initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
  
aWin’s setBackgroundColor:(current application’s NSColor’s whiteColor())
  
  
aWin’s setTitle:aTitle
  
aWin’s setDelegate:me
  
aWin’s setDisplaysWhenScreenProfileChanges:true
  
aWin’s setHasShadow:true
  
aWin’s setIgnoresMouseEvents:false
  
aWin’s setLevel:(current application’s NSNormalWindowLevel)
  
aWin’s setOpaque:false
  
aWin’s setReleasedWhenClosed:true
  
aWin’s |center|()
  
aWin’s makeKeyAndOrderFront:(me)
  
  
aWin’s setContentView:aView
  
  
return aWin
  
end makeWinWithView

–close win
on closeWin:aWindow
  repeat with n from 10 to 1 by -1
    (aWindow’s setAlphaValue:n / 10)
    
delay 0.02
  end repeat
  
aWindow’s |close|()
end closeWin:

★Click Here to Open This Script 

2015/12/08 都道府県リストから隣接都道府県を含む該当のコードを抽出する

都道府県コードを指定すると、「当該都道府県」+「隣接都道府県」のコードリストを抽出するAppleScriptです。

休みの日に、

「全国の『戦場の絆』が導入されているゲーセンの最寄駅までの距離をすべて計算し、一番辺鄙(へんぴ)な場所にあるゲーセンを算出しよう!」

と思い立ち(余計なお世話)、日本全国の駅のリスト(9,000件以上)を「駅データ.jp」から取得し、全国のゲーセンの住所(から算出した緯度、経度情報)と距離を測って最寄り駅までの距離を求めてみました。

farestgamecenter.png
▲最寄駅が最も遠いゲームセンター「サンゲームス鹿屋」。最寄駅まで23km。ただし、地元の人たちは車で移動しているのでまったく問題ないことが航空写真からもわかる

最初に組んだバージョンは、780箇所ぐらいのゲーセンと日本全国の駅(9,000件以上)の距離をすべて計算する仕様になっていたので、距離計算にCore i7 2.6GHzのMacBook Pro Retinaでも90分ぐらいかかりました(さすがに700万回距離計算したので)。手っ取り早くプログラムを組むことを重視したので、膨大な計算時間も仕方ないところですが、これではとうてい実用的とはいえません。

free_034_005.png

何も東京のド真ん中にある地点と北海道の最果てにある駅の距離を計測しなくても、そこが「最寄駅」ではないことぐらいは明白なので(ただし、用途による)、東京+隣接する都道府県だけに検索範囲をしぼってあげることで大幅な計算の高速化が行えるだろう、とあたりをつけ、47都道府県の隣接都道府県のテーブル(これ)を作ってみました。

次に、このテーブルを用いて「ゲーセンの所在する県+まわりの都道府県(隣接都道府県)」だけの距離計算を行うようにして、実際に圧倒的に演算量を減らすことに成功。わずか10分強で計算完了しました。

さらに、「毎回駅データの抽出を行わずに、県ごとにループして最初だけ抽出処理」するようにしたら、5分ぐらいで計算が完了。当初の90分から大幅な高速化を行うことができました。

最寄駅を求める、といったぐらいの演算であれば、こうして工夫することで計算量を大幅に減らせるので便利です。

目下、かんたんに複数CPUコアに処理を割り振る並列処理化AppleScriptを整備してあり、さまざまな並列処理タスクを実行した経験からいえば・・・4コア8スレッドのCore i7のMacBook Proで並列処理すれば、シングルコアでの実行時間(5分)の半分程度(2〜3分)の処理時間にはなるものと予想されます。

それよりも多いコア数(Mac Proなど)で並列処理実行した場合の性能は不明ですが、とくにノート型では複数コアで並列処理した際に、コアの温度上昇による処理能力の全体的な低下(サーマルスロットリング)が顕著であり、期待値どおりの処理性能を引き出すことは困難です(システム全体では処理能力に余裕があっても、負荷を増やしてすべてを埋め尽くすのは難しい)。

このため、デスクトップ型用のマルチコアCPUで処理を行うと、ノート型用のCPUよりも顕著な処理性能の向上が期待できます(持ってないので、やったことがないですけれども)。

用途によっては、長崎県と大分県は陸路でつながっているので、隣接県として扱ってもよいかもしれないですし、鹿児島県の一部は沖縄県と近いので隣接県として扱っておく必要があるかもしれません。そのあたりは「用途次第」でしょう。さらに、北海道新幹線の開業によって、北海道と青森県が隣接している、とみなすことも可能に。

AppleScript名:都道府県リストから隣接都道府県を含む該当のコードを抽出する
– Created 2015-12-06 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

property prefList : {{prefCode:1, prefName:“北海道”, neighbors:{}}, {prefCode:2, prefName:“青森県”, neighbors:{3, 5}}, {prefCode:3, prefName:“岩手県”, neighbors:{2, 4, 5}}, {prefCode:4, prefName:“宮城県”, neighbors:{3, 5, 6, 7}}, {prefCode:5, prefName:“秋田県”, neighbors:{2, 3, 4, 6}}, {prefCode:6, prefName:“山形県”, neighbors:{3, 4, 5, 7, 15}}, {prefCode:7, prefName:“福島県”, neighbors:{4, 6, 8, 9, 10, 15}}, {prefCode:8, prefName:“茨城県”, neighbors:{7, 9, 10, 11, 12}}, {prefCode:9, prefName:“栃木県”, neighbors:{7, 8, 10, 11, 12}}, {prefCode:10, prefName:“群馬県”, neighbors:{7, 9, 11, 15, 20}}, {prefCode:11, prefName:“埼玉県”, neighbors:{8, 9, 10, 12, 13, 19, 20}}, {prefCode:12, prefName:“千葉県”, neighbors:{8, 11, 13}}, {prefCode:13, prefName:“東京都”, neighbors:{11, 12, 19, 14}}, {prefCode:14, prefName:“神奈川県”, neighbors:{13, 19, 22}}, {prefCode:15, prefName:“新潟県”, neighbors:{6, 7, 10, 16, 20}}, {prefCode:16, prefName:“富山県”, neighbors:{15, 17, 20, 21}}, {prefCode:17, prefName:“石川県”, neighbors:{16, 18, 21}}, {prefCode:18, prefName:“福井県”, neighbors:{17, 21, 25, 26}}, {prefCode:19, prefName:“山梨県”, neighbors:{11, 13, 14, 20, 22}}, {prefCode:20, prefName:“長野県”, neighbors:{10, 11, 15, 16, 19, 21, 22, 23}}, {prefCode:21, prefName:“岐阜県”, neighbors:{16, 17, 18, 20, 23, 24, 25}}, {prefCode:22, prefName:“静岡県”, neighbors:{14, 19, 20, 23}}, {prefCode:23, prefName:“愛知県”, neighbors:{20, 21, 22, 24}}, {prefCode:24, prefName:“三重県”, neighbors:{21, 23, 25, 26, 29}}, {prefCode:25, prefName:“滋賀県”, neighbors:{18, 21, 24, 26}}, {prefCode:26, prefName:“京都府”, neighbors:{18, 24, 25, 27, 28, 29}}, {prefCode:27, prefName:“大阪府”, neighbors:{26, 29, 28, 30}}, {prefCode:28, prefName:“兵庫県”, neighbors:{26, 27, 31, 33}}, {prefCode:29, prefName:“奈良県”, neighbors:{24, 25, 26, 27, 30}}, {prefCode:30, prefName:“和歌山県”, neighbors:{24, 27, 29}}, {prefCode:31, prefName:“鳥取県”, neighbors:{28, 33, 32, 34}}, {prefCode:32, prefName:“島根県”, neighbors:{31, 34, 35}}, {prefCode:33, prefName:“岡山県”, neighbors:{28, 31, 34}}, {prefCode:34, prefName:“広島県”, neighbors:{33, 31, 32, 35}}, {prefCode:35, prefName:“山口県”, neighbors:{32, 34}}, {prefCode:36, prefName:“徳島県”, neighbors:{37, 39}}, {prefCode:37, prefName:“香川県”, neighbors:{36, 38, 39}}, {prefCode:38, prefName:“愛媛県”, neighbors:{37, 39}}, {prefCode:39, prefName:“高知県”, neighbors:{36, 37, 38}}, {prefCode:40, prefName:“福岡県”, neighbors:{44, 43, 41}}, {prefCode:41, prefName:“佐賀県”, neighbors:{40, 42}}, {prefCode:42, prefName:“長崎県”, neighbors:{41}}, {prefCode:43, prefName:“熊本県”, neighbors:{40, 42, 44, 45, 46}}, {prefCode:44, prefName:“大分県”, neighbors:{40, 43, 45}}, {prefCode:45, prefName:“宮崎県”, neighbors:{43, 44, 46}}, {prefCode:46, prefName:“鹿児島県”, neighbors:{43, 45}}, {prefCode:47, prefName:“沖縄県”, neighbors:{}}}

set aPref to 13 –Tokyo
set aRes to my filterRecListByLabel2(prefList, “prefCode == [c]%@”, {aPref})
set targList to neighbors of aRes & aPref
–>  {11, 12, 19, 14, 13}

–リストに入れたレコードを、指定の属性ラベルの値で抽出(predicateとパラメータを分離)し、1つのアイテムだけを返す
on filterRecListByLabel2(aRecList as list, aPredicate as string, aParam)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate argumentArray:aParam
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
set bList to ASify from filteredArray as list
  
set cList to first item of bList
  
return cList
end filterRecListByLabel2

★Click Here to Open This Script 

2015/12/08 データリストに合致するものを抽出

レコードのリストから、指定フィールドに指定した複数の値に該当するレコードを抽出するAppleScriptです。

NSPredicateを用いて、さまざまな凝った抽出条件が指定できますが、”IN”演算子であとにリストを指定するやり方が面倒だったので、NSCompoundPredicateにOR条件で複数条件を合成して検索するようにしてみました。

AppleScript名:データリストに合致するものを抽出
– Created 2015-12-06 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use BridgePlus : script "BridgePlus"

set prefList to {{station_name_k:"", lon:"140.726413", add:"北海道函館市若松町12-13", station_cd:"1110101", lat:"41.773709", e_sort:"1110101", close_ymd:"", station_g_cd:"1110101", station_name:"函館", e_status:"0", line_cd:"11101", open_ymd:"1902-12-10", station_name_r:"", post:"040-0063", pref_cd:"1"}, {station_name_k:"", lon:"140.733539", add:"函館市亀田本町", station_cd:"1110102", lat:"41.803557", e_sort:"1110102", close_ymd:"", station_g_cd:"1110102", station_name:"五稜郭", e_status:"0", line_cd:"11101", open_ymd:"", station_name_r:"", post:"041-0813", pref_cd:"2"}}

set aRes to my filterDictArrayByLabel3(prefList, "pref_cd == %@", {"1"})
–>  {{post:"040-0063", lon:"140.726413", add:"北海道函館市若松町12-13", station_cd:"1110101", lat:"41.773709", e_sort:"1110101", close_ymd:"", station_g_cd:"1110101", station_name:"函館", e_status:"0", line_cd:"11101", open_ymd:"1902-12-10", station_name_r:"", pref_cd:"1", station_name_k:""}}

set bRes to my filterDictArrayByLabel3(prefList, "pref_cd == %@", {"1", "2"})
–>  {{post:"040-0063", lon:"140.726413", add:"北海道函館市若松町12-13", station_cd:"1110101", lat:"41.773709", e_sort:"1110101", close_ymd:"", station_g_cd:"1110101", station_name:"函館", e_status:"0", line_cd:"11101", open_ymd:"1902-12-10", station_name_r:"", pref_cd:"1", station_name_k:""}, {post:"041-0813", lon:"140.733539", add:"函館市亀田本町", station_cd:"1110102", lat:"41.803557", e_sort:"1110102", close_ymd:"", station_g_cd:"1110102", station_name:"五稜郭", e_status:"0", line_cd:"11101", open_ymd:"", station_name_r:"", pref_cd:"2", station_name_k:""}}

–リストに入れたレコードを、指定の属性ラベルの値で抽出(predicateとパラメータを分離、複数条件をORでつなぐ)
on filterDictArrayByLabel3(aList, aPredicate as string, aParam)
  set aArray to current application’s NSArray’s arrayWithArray:aList
  
set predList to {}
  
repeat with i in aParam
    set j to i as text
    
set the end of predList to (current application’s NSPredicate’s predicateWithFormat:aPredicate argumentArray:{j})
  end repeat
  
set pred to current application’s NSCompoundPredicate’s orPredicateWithSubpredicates:predList
  
set filteredArray to aArray’s filteredArrayUsingPredicate:pred
  
set bList to ASify from filteredArray as list
  
return bList
end filterDictArrayByLabel3

★Click Here to Open This Script 

2015/12/06 現在地点の緯度経度情報を取得する v2

Cocoaの機能を用いて、Macの現在位置の緯度経度情報を取得するAppleScriptです。実行時にWiFiがオンになっている必要があります。

だんだん、ASOCのScriptも書きこなれてきて、「この場合にはこう書く」的な方法論が蓄積され、便利に書けるようになってきました。処理系の方もCocoaを用いた記述に最適化されてきているのでしょうか(OS X 10.10ではForegroundで実行することを要求されましたが、OS X 10.11ではとくに文句も言われないので、、、)。

AppleScript名:GetCurrentLocation_ElCapitan v4
– Created 2015-03-04 by Takaaki Naganoya, Shane Stanley
– Modified 2015-12-06 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “CoreLocation”

property locationManager : missing value
property curLatitude : 0
property curLongitude : 0

set {aLat, aLong} to getCurrentGeoLocation() of me

on getCurrentGeoLocation()
  set locationManager to current application’s CLLocationManager’s alloc()’s init()
  
  
set locE to locationManager’s locationServicesEnabled()
  
if (locE as boolean) = true then
    locationManager’s setDelegate:me
    
locationManager’s setDesiredAccuracy:(current application’s kCLLocationAccuracyNearestTenMeters)
    
locationManager’s setDistanceFilter:500
    
locationManager’s startUpdatingLocation()
  else
    return false –error in init
  end if
  
  
set hitF to false
  
repeat 3000 times
    if {curLatitude, curLongitude} is not equal to {0, 0} then
      set hitF to true
      
exit repeat
    end if
    
delay 0.01
  end repeat
  
  
if hitF = false then return false
  
return {curLatitude, curLongitude}
end getCurrentGeoLocation

on locationManager:manager didUpdateLocations:locations
  set location to (locations’s lastObject())
  
set eventDate to (location’s timestamp())
  
set howRecent to (eventDate’s timeIntervalSinceNow())
  
set howRecent to howRecent as real
  
set howRecent to absNum(howRecent)
  
  
if howRecent < 15.0 then
    set alt to location’s altitude –>(NSNumber) 46.356517791748
    
set aSpeed to location’s speed –>(NSNumber) -1.0
    
set aCourse to location’s course –North:0, East:90, South:180, West:270
    
–>  (NSNumber) -1.0
    
set theDescription to location’s |description|()
    
–> (NSString) “< +35.xxxxx,+139.xxxxxx> +/- 65.00m (speed -1.00 mps / course -1.00) @ 2015/03/04 8時56分41秒 日本標準時”
    
set anNSScanner to current application’s NSScanner’s scannerWithString:theDescription
    
anNSScanner’s setCharactersToBeSkipped:(current application’s NSCharacterSet’s characterSetWithCharactersInString:“< ,")
    
set {theResult, aLat} to anNSScanner’s scanDouble:(reference)
    
set {theResult, aLng} to anNSScanner’s scanDouble:(reference)
    
copy {aLat, aLng} to {my curLatitude, my curLongitude}
  else
    locationManager’s stopUpdatingLocation()
  end if
  
end locationManager:didUpdateLocations:

on locationManager:anCLLocationManager didFailWithError:anNSError
  display dialog (anNSError’s localizedDescription() as text)
end locationManager:didFailWithError:

on absNum(aNum)
  if aNum > 0 then
    return aNum
  else
    return (aNum * -1)
  end if
end absNum

★Click Here to Open This Script 

2015/12/04 AlphabetとNumericの混在かどうかを調べる

Cocoaの機能を用いて、アルファベットと数字の混在状態になっているかどうかを調べるAppleScriptです。

AppleScript名:AlphabetとNumericの混在かどうかを調べる
– Created 2015-12-04 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aRes to chkMixtureOfNumericAndAlphabet(“ABC”) of me
–>  false

set aRes to chkMixtureOfNumericAndAlphabet(“123″) of me
–>  false

set aRes to chkMixtureOfNumericAndAlphabet(“4f73vg1v”) of me –Target
–>  true

set aRes to chkMixtureOfNumericAndAlphabet(“4f73vg1vあああ”) of me
–>  false

–数字とアルファベットの混在状態の時にtrueを返す
on chkMixtureOfNumericAndAlphabet(checkString)
  set a0Res to chkAlphabetAndNumeric(checkString) of me
  
set a1Res to chkNumeric(checkString) of me
  
set a2Res to chkAlphabet(checkString) of me
  
  
if {a0Res, a1Res, a2Res} = {true, false, false} then
    return true
  else
    return false
  end if
  
end chkMixtureOfNumericAndAlphabet

–数字のみかを調べて返す
on chkNumeric(checkString)
  set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:“0123456789″
  
set ret to my chkCompareString:checkString baseString:digitCharSet
  
return ret as boolean
end chkNumeric

– アルファベットのみか調べて返す
on chkAlphabet(checkString)
  set aStr to current application’s NSString’s stringWithString:checkString
  
set allCharSet to current application’s NSMutableCharacterSet’s alloc()’s init()
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of “a”, 26))
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of “A”, 26))
  
set aBool to my chkCompareString:aStr baseString:allCharSet
  
return aBool as boolean
end chkAlphabet

– アルファベットと数字のみか調べて返す
on chkAlphabetAndNumeric(checkString)
  set aStr to current application’s NSString’s stringWithString:checkString
  
set allCharSet to current application’s NSMutableCharacterSet’s alloc()’s init()
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of “0″, 10))
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of “a”, 26))
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of “A”, 26))
  
set aBool to my chkCompareString:aStr baseString:allCharSet
  
return aBool as boolean
end chkAlphabetAndNumeric

on chkCompareString:checkString baseString:baseString
  set aScanner to current application’s NSScanner’s localizedScannerWithString:checkString
  
aScanner’s setCharactersToBeSkipped:(missing value)
  
aScanner’s scanCharactersFromSet:baseString intoString:(missing value)
  
return (aScanner’s isAtEnd()) as boolean
end chkCompareString:baseString:

★Click Here to Open This Script 

2015/12/04 FineReader OCR Proで指定文字列から言語指定オブジェクトを生成する

FineReader OCR Proで、言語指定文字列から実際に言語指定オブジェクトを生成するAppleScriptです。

FineReader OCR Proにはいろいろと不可解な仕様があり・・・とくに、言語を文字列で(”日本語”とか)指定すると、実際に指定するオブジェクト(Japaneseとか)を指定できそうな気配がするものの、実際に取得できるものはオブジェクトを文字列化したものであり、そのまま認識言語として指定できるものではありません。

そこで、テキストで指定した言語名からFineReader OCR Proのオブジェクトを生成するルーチンを作ってみました。

繰り返しになりますがFineReader OCR ProでAppleScriptから「C/C++」という言語は指定できません。

AppleScript名:FineReader OCR Proで指定文字列から言語指定オブジェクトを生成する
– Created 2015-12-04 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation” –ASOCの機能は一切使っていないが、ASOC環境で問題なく実行できることを確認

set langList to {“C/C++”, “C++”, “Basic”, “Java”, “Fortran”, “COBOL”, “Pascal”}
set aList to {}

tell application id “com.abbyy.FineReaderPro”
  
  
repeat with i in langList
    set j to contents of i
    
set tmpObj to getLanguageObjFromString(j) of me
    
if tmpObj is not equal to “” then
      set the end of aList to tmpObj
    end if
  end repeat
  
  
return aList
  
–> {Basic, Java, Fortran, Cobol, Pascal}
end tell

on getLanguageObjFromString(aStr)
  tell application id “com.abbyy.FineReaderPro”
    set alangID to get language id from language name aStr –この命令が使いものにならない。オブジェクトではなく文字列を返してくる
    
if alangID = missing value then return “”
    
    
set aScript to “tell application id \”com.abbyy.FineReaderPro\” to “ & alangID
    
    
try
      set aRes to run script aScript
    on error
      return “”
    end try
    
    
return aRes
  end tell
end getLanguageObjFromString

★Click Here to Open This Script 

2015/12/04 Photosで選択中の写真の撮影場所が指定地点から50メートル以内の場合には書き出してFineReader OCR ProでOCR処理 v2

Photos(日本語名:写真.app)で選択中の写真から緯度/経度情報を取得して、その撮影場所が指定場所から50メートル以内の範囲にあれば、画像書き出しを行って、FineReader OCR Proで画像の文字認識を行い、必要な情報(「戦場の絆」のリプレイID)を取得するAppleScriptの改良版です。

ゲームセンターで「戦場の絆」をプレイして、YouTubeに自動であがっているリプレイムービーにアクセスするために発行される「リプレイID」。

ocr3.png

これをiPhoneで写真に撮って自宅で検索ページのフォームに手入力して、リプレイムービーを見ていたわけですが、OCRをAppleScriptから呼び出すことで、手入力の手間をなくすことを目的としています。また、行きつけのゲームセンター「ではない」場所で撮影した写真を認識させることを防ぐために、写真からGPS情報を取得して指定場所との距離を計算して「50メートル以内」であれば処理するようにしています。

前バージョンでは、文字認識内容の一部に誤認識がありました。十分な文字サイズで撮影しているにもかかわらず、FineReader OCR Proの文字認識エンジンの性能を考えるとありえない誤認識でした(”1″と”t”を間違えていた)。

この認識ターゲットは「英数字から構成されるものの英単語そのものではない」ため、認識ターゲット言語が「English」では、英語の辞書によるスペルチェックや「英語の性格上、ありえない文字の並び」は排除されているものと考え、FineReader OCR Proがサポートしている「プログラミング言語」(画面上では、翻訳ミスで「公式言語」になっていますが、、、、)を指定すればよいのではないかと考えました。

ocr1.png

そこで、Basic、Java、Fortran、Cobol、Pascalの5つを指定してみたところ、今度はバッチリ間違いなく認識しました。

ocr2.png

ちなみに、FineReader OCR Proでは「C++」もサポートしているらしいのですが、C++という予約語があったとしても、そんなもん構文チェックでエラーになるに決まっています。

せめて「Cplusplus」といった予約語だったら指定できるはずですが・・・・

AppleScript名:Photosで選択中の写真が指定場所から50メートル以内の場合には書き出してFineReader OCR Proで処理 v2.0
– Created 2015-12-04 by Takaaki Naganoya –v2.0
– getDistanceBetweenTwoPlaces : Created 2015-03-03 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “CoreLocation”
use framework “QuartzCore”

set targPlace to {35.745769, 139.675565} –Game City Itabashi
set bLoc to getSelectionOnPhotos() of me –Get Location From Photo
set aDist to getDistanceBetweenTwoPlaces(targPlace, bLoc) of me

–指定地点から50メートル以内の距離で撮影された写真であれば、Exportして後続の処理を行う
if aDist is equal to false or aDist > 50 then return false

–選択中の写真のうち最初のものだけExport
set targPhotoAlias to exportSelectedPhotoOnPhotos() of me

–実験に用いた写真はそのままではOCR処理できなかったので、手っ取り早く階調反転を行う
set invertRes to convAsFilteredJPEG(targPhotoAlias, “CIColorInvert”) of me
if invertRes = false then
  display dialog “Error in CIImage” with title “Error” buttons {“OK”} default button 1 with icon 2
  
return
end if
set invImage to (POSIX file invertRes) as alias

set outFileName to (current application’s NSUUID’s UUID()’s UUIDString() as text) & “.txt”
set outPath to ((path to desktop) as text) & outFileName –sandbox環境ではパスは無視される

tell application id “com.abbyy.FineReaderPro”
  
  
–Check Ready
  
set getReady to (is finereader controller active)
  
if getReady = false then return
  
  
–set idList to {English} –英語だとスペルチェックされて、”1″と”t”を誤認識するため英語の指定をやめた
  
–Caution: this command is executed asynchronously
  
–Caution: export command was changed in v12.1.4
  
  
set idList to {Basic, Java, Fortran, Cobol, Pascal} –認識ターゲット言語(C++の予約語はあるようだが、指定できないよ!)
  
open invImage – In this version ,we have to open image, at first.
  
export to txt outPath ocr languages enum idList retain layout (as plain text) encoding (utf8)
  
  
–Wait for finish
  
repeat 30 times
    set curStat to (is busy)
    
if curStat = false then exit repeat
    
delay 1
  end repeat
  
  
–sandbox環境(Mac App Store版)の場合にはファイル出力先を別途取得
  
set sandRes to (is sandboxed)
  
if sandRes = true then
    set outDir to (get output dir) as text
    
set outPath to (outDir & outFileName) as text
  end if
  
end tell

–OCR処理した結果のテキストを読み込む
tell current application
  set textRes to read file outPath
end tell

–数字ではじまる行のみを抽出して、リプレイIDを取得
set textList to paragraphs of textRes
set outList to {}
repeat with i in textList
  set j to contents of i
  
if length of j > 8 then
    set firstChar to first character of j
    
set nRes to chkNumeric(firstChar) of me
    
if nRes = true then
      set tmp1 to text 2 thru -1 of j –最初の数字を除去
      
set tmp2 to returnNumberAndAlphabetCharsOnly(tmp1) of me
      
if length of tmp2 8 then
        set tmp3 to text 1 thru 8 of tmp2
        
set the end of outList to tmp3
      end if
    end if
  end if
end repeat

outList
–> {”4f73vg1v”, “v3v32zt3″, “yk1z371x”, “52yzvn11″, “k1ftfvvg”}–Previous Version (3rd ID is wrong)
–> {”4f73vg1v”, “v3v32zt3″, “yk1z37tx”, “52yzvn11″, “k1ftfvvg”}– This Version (Perfect!!!!)

–Photosで選択中の写真の1枚目(複数時には無視)から緯度、経度情報を取得する
on getSelectionOnPhotos()
  tell application “Photos”
    set aa to selection
    
if aa = {} or aa = missing value then return false
    
set a to first item of aa
    
set aProp to properties of a
    
    
set aLoc to location of aProp
    
return aLoc
  end tell
end getSelectionOnPhotos

–2点間の距離を計算する
on getDistanceBetweenTwoPlaces(aPlaceLoc, bPlaceLoc)
  try
    set {aLat, aLong} to aPlaceLoc
    
set {bLat, bLong} to bPlaceLoc
  on error
    return false
  end try
  
  
set aPlace to current application’s CLLocation’s alloc()’s initWithLatitude:aLat longitude:aLong
  
set bPlace to current application’s CLLocation’s alloc()’s initWithLatitude:bLat longitude:bLong
  
set distanceInMetres to aPlace’s distanceFromLocation:bPlace
  
return (distanceInMetres as real)
end getDistanceBetweenTwoPlaces

–Photos上で選択中の写真をTemporary Folderに掘ったフォルダに書き出して、そのalias情報を返す
on exportSelectedPhotoOnPhotos()
  set dtPath to (path to temporary items) as text
  
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
  
  
set dirPath to ((POSIX path of dtPath) & aUUID)
  
set fileManager to current application’s NSFileManager’s defaultManager()
  
set aRes to (fileManager’s createDirectoryAtPath:dirPath withIntermediateDirectories:true attributes:(missing value) |error|:(reference))
  
set dtPath to dtPath & aUUID
  
  
tell application “Photos”
    set a to selection
    
if a = {} then return
    
set aRes to (export a to file dtPath)
  end tell
  
  
tell application “Finder”
    tell folder dtPath
      set fList to (every file) as alias list
    end tell
  end tell
  
  
if fList = {} then return false
  
return first item of fList
end exportSelectedPhotoOnPhotos

–CIFilterをかけたJPEG画像を生成
–参照:http://ashplanning.blogspot.jp/ のうちのどこか
on convAsFilteredJPEG(aPath, aFilterName)
  
  
–aliasをURL(input)とPOSIX path(output) に変換
  
set aURL to (current application’s |NSURL|’s fileURLWithPath:(POSIX path of aPath)) –Input
  
set aPOSIX to (POSIX path of aPath) & “_” & aFilterName & “.jpg” –Output
  
  
–CIImageを生成
  
set aCIImage to current application’s CIImage’s alloc()’s initWithContentsOfURL:aURL
  
  
– CIFilter をフィルタの名前で生成
  
set aFilter to current application’s CIFilter’s filterWithName:aFilterName
  
aFilter’s setDefaults() –各フィルタのパラメータはデフォルト
  
  
–Filterを実行
  
aFilter’s setValue:aCIImage forKey:“inputImage”
  
set aOutImage to aFilter’s valueForKey:“outputImage”
  
  
– NSBitmapImageRep を CIImage から生成
  
set aRep to current application’s NSBitmapImageRep’s alloc()’s initWithCIImage:aOutImage
  
  
– NSBitmapImageRep から JPEG データを取得
  
set jpegData to aRep’s representationUsingType:(current application’s NSJPEGFileType) |properties|:(missing value)
  
  
– ファイルに保存
  
set fsRes to jpegData’s writeToFile:aPOSIX atomically:true
  
if (fsRes as boolean) = false then return false –失敗した場合
  
return aPOSIX –成功した場合
  
end convAsFilteredJPEG

–数字のみかを調べて返す
on chkNumeric(checkString)
  set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:“0123456789″
  
set ret to my chkCompareString:checkString baseString:digitCharSet
  
return ret as boolean
end chkNumeric

on chkCompareString:checkString baseString:baseString
  set aScanner to current application’s NSScanner’s localizedScannerWithString:checkString
  
aScanner’s setCharactersToBeSkipped:(missing value)
  
aScanner’s scanCharactersFromSet:baseString intoString:(missing value)
  
return (aScanner’s isAtEnd()) as boolean
end chkCompareString:baseString:

–文字置換
on repChar(aStr, targStr, repStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set bString to aString’s stringByReplacingOccurrencesOfString:targStr withString:repStr
  
return bString as text
end repChar

–アルファベットと数字以外を削除して返す
on returnNumberAndAlphabetCharsOnly(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[^0-9A-Za-z]” withString:“” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
return anNSString as text
end returnNumberAndAlphabetCharsOnly

★Click Here to Open This Script 

2015/12/02 Photosで選択中の写真の撮影場所が指定地点から50メートル以内の場合には書き出してFineReader OCR ProでOCR処理

多言語OCRソフト「FineReader OCR Pro」を用いて、現代のAppleScriptに備わっているさまざまな機能を利用した処理を行ってみました。

Photos(日本語名:写真.app)で選択中の写真から緯度/経度情報を取得して、その撮影場所が指定場所から50メートル以内の範囲にあれば、画像書き出しを行って、FineReader OCR Proで画像の文字認識を行い、必要な情報(「戦場の絆」のリプレイID)を取得します。

img_2980.jpg

これまでは、リプレイのための情報をゲーセンの端末で表示させて、それをiPhoneで写真に撮り、自宅で手入力していたのですが・・・これが、ものすごく不毛な作業なので、手入力しなくてもいいようにAppleScriptを書いてみました。

gamecity.jpg

step1:写真.app(Photos.app)の撮影場所の取得

まず、ゲーセンで撮影した写真かどうか、撮影時に記録されているGPS情報から判定します。
Photos上で選択中の写真からの緯度/経度情報の取得はとくに問題はありません。ふつーにできます(ただし、OS X 10.11上の最新版が必要。OS X 10.10上のものは機能が不完全で使用不可)。Photos.appで選択中の写真のファイル名やファイルパスを求めることはできませんが、それは指定の写真がローカルにあるのかCloudにあるのかわからないためで、実際の写真にアクセスするためにはいったんexportする必要があるというのがPhotos.appの流儀です。

fine1.png

2点間の距離を求める処理も、CoreLocationの機能を用いて余裕でできます。

step2:写真のフィルター処理

写真を書き出してそのままOCR処理・・・したかったのですが、そのままだとFineReader OCR Proが「OCR処理できない」と言ってきたので(白地に黒っぽい文字の構成でないと認識しないらしい)、CIFilterを用いて階調反転を行い、その処理結果をFineReader OCR Proに渡しています。画像の編集機能はFineReader OCR Proに内蔵されており階調反転も行えるのですが、この機能はAppleScriptから呼び出せません。それでも、AppleScript単体で画像処理できるので問題ありません。Photoshopを併用する必要もありません。

step3:OCR処理

以前に作成した文字認識Scriptを組み合わせてすぐにOCR処理できるか・・・と、思いきや、なんと以前のバージョンのScriptだとそのまま実行してくれませんでした。バージョンアップ時にAppleScript用語辞書が変更になったらしく、そのままだと構文確認(コンパイル)をパスしません。

最初に画像を別途オープンする必要があるんだな、とあたりをつけて・・・画像をopenさせてみたら大丈夫でした(ここちょっと、難易度高いです)。

fine2.png

OCR処理する場合に、今回は日本語の文字列が不要だったので、認識言語に「English」のみを指定してみました。ログ表示させたまま実行すると文字化けした文字が表示されますが、これで正しい動作です。

kizuna.png

step4:認識結果のテキストファイルへの出力

認識結果をテキストファイルに出力させているのですが、FineReader OCR ProがMac App Store版であるため(以前にしらべたお試し版と異なり)Sandbox化されています。このため、出力先が極度に限定されており、アプリケーションに出力フォルダを聞いて、そこに指定ファイル名のテキストがあるものと仮定して処理しています。

その後、ごにょごにょと文字列の抽出処理を行って、希望のリプレイIDを取得できました。ここまでくれば、リプレイムービーをYouTubeからダウンロードするのも余裕です(リプレイムービー検索ページのFormにIDを突っ込んでYouTubeのムービーのURLを取得する未公開ルーチンが存在しているため)。

fine3.png

こういうレベルの処理がAppleScriptでさっくり書けるようになったことは、素直に喜びたいと思います。

FineReader OCR Proについて

万能無敵のFineReader OCR Proですが、いただけない点もあります。OCR処理したあとの画像をクローズしたり捨てるためのAppleScriptの命令が存在せず、複数の画像を連続認識するような場合にはこれでは困ります(processをkillするしかなさそう)。メーカーにフィードバックしておかないと、、、、(しました)。

cannotclose.png

また、できればFineReader OCR Pro内の階調反転機能は呼び出せたほうがよいものと思われます。今回はあらかじめ画像の下地が濃い色(Whiteではない)だとわかっていたので決めうちで階調反転させましたが、ここの判断まで自動化したいのであれば、Photoshopが持っているような明度別ヒストグラムの集計処理が利用できたほうがよいでしょう(認識エラーが出たら階調反転させる、という場当たり的な処理でもOKといえばOK)。

今回のScriptでは「ゲームシティ板橋店」からの距離を計算しましたが、全国の「戦場の絆」設置ゲームセンターの住所はわかっているので、これらをすべてデータで持って、緯度経度情報をすべて照合しても、たいした時間はかからないと思われます(後日、これを行ってGoogle API経由で住所ジオコーダーを呼び出して、3箇所だけどうしても緯度・経度情報に変換できない住所がありました。田んぼのまんなかに作ったショッピングセンター的なもので、最近作られた地名のもよう。意外と苦労しました、、、)。

AppleScript名:Photosで選択中の写真が指定場所から50メートル以内の場合には書き出してFineReader OCR Proで処理
– Created 2015-12-02 by Takaaki Naganoya –v1.1.1
– getDistanceBetweenTwoPlaces : Created 2015-03-03 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “CoreLocation”
use framework “QuartzCore”

set targPlace to {35.745769, 139.675565} –Game City Itabashi
set bLoc to getSelectionOnPhotos() of me –Get Location From Photo
set aDist to getDistanceBetweenTwoPlaces(targPlace, bLoc) of me

–指定地点から50メートル以内の距離で撮影された写真であれば、Exportして後続の処理を行う
if aDist is equal to false or aDist > 50 then return false

–選択中の写真のうち最初のものだけExport
set targPhotoAlias to exportSelectedPhotoOnPhotos() of me

–実験に用いた写真はそのままではOCR処理できなかったので、手っ取り早く階調反転を行う
set invertRes to convAsFilteredJPEG(targPhotoAlias, “CIColorInvert”) of me
if invertRes = false then
  display dialog “Error in CIImage” with title “Error” buttons {“OK”} default button 1 with icon 2
  
return
end if
set invImage to (POSIX file invertRes) as alias

set outFileName to (current application’s NSUUID’s UUID()’s UUIDString() as text) & “.txt”
set outPath to ((path to desktop) as text) & outFileName –sandbox環境ではパスは無視される

tell application id “com.abbyy.FineReaderPro”
  
  
–Check Ready
  
set getReady to (is finereader controller active)
  
if getReady = false then return
  
  
set idList to {English} –認識ターゲット言語
  
–Caution: this command is executed asynchronously
  
–Caution: export command was changed in v12.1.4
  
  
open invImage – In this version ,we have to open image, at first.
  
export to txt outPath ocr languages enum idList retain layout (as plain text) encoding (utf8)
  
  
–Wait for finish
  
repeat 30 times
    set curStat to (is busy)
    
if curStat = false then exit repeat
    
delay 1
  end repeat
  
  
–sandbox環境(Mac App Store版)の場合にはファイル出力先を別途取得
  
set sandRes to (is sandboxed)
  
if sandRes = true then
    set outDir to (get output dir) as text
    
set outPath to (outDir & outFileName) as text
  end if
  
end tell

–OCR処理した結果のテキストを読み込む
tell current application
  set textRes to read file outPath
end tell

–数字ではじまる行のみを抽出して、リプレイIDを取得
set textList to paragraphs of textRes
set outList to {}
repeat with i in textList
  set j to contents of i
  
if length of j > 8 then
    set firstChar to first character of j
    
set nRes to chkNumeric(firstChar) of me
    
if nRes = true then
      set tmp1 to text 2 thru -1 of j –最初の数字を除去
      
set tmp2 to returnNumberAndAlphabetCharsOnly(tmp1) of me
      
if length of tmp2 8 then
        set tmp3 to text 1 thru 8 of tmp2
        
set the end of outList to tmp3
      end if
    end if
  end if
end repeat

outList
–> {”4f73vg1v”, “v3v32zt3″, “yk1z371x”, “52yzvn11″, “k1ftfvvg”}

–Photosで選択中の写真の1枚目(複数時には無視)から緯度、経度情報を取得する
on getSelectionOnPhotos()
  tell application “Photos”
    set aa to selection
    
if aa = {} or aa = missing value then return false
    
set a to first item of aa
    
set aProp to properties of a
    
    
set aLoc to location of aProp
    
return aLoc
  end tell
end getSelectionOnPhotos

–2点間の距離を計算する
on getDistanceBetweenTwoPlaces(aPlaceLoc, bPlaceLoc)
  try
    set {aLat, aLong} to aPlaceLoc
    
set {bLat, bLong} to bPlaceLoc
  on error
    return false
  end try
  
  
set aPlace to current application’s CLLocation’s alloc()’s initWithLatitude:aLat longitude:aLong
  
set bPlace to current application’s CLLocation’s alloc()’s initWithLatitude:bLat longitude:bLong
  
set distanceInMetres to aPlace’s distanceFromLocation:bPlace
  
return (distanceInMetres as real)
end getDistanceBetweenTwoPlaces

–Photos上で選択中の写真をTemporary Folderに掘ったフォルダに書き出して、そのalias情報を返す
on exportSelectedPhotoOnPhotos()
  set dtPath to (path to temporary items) as text
  
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
  
  
set dirPath to ((POSIX path of dtPath) & aUUID)
  
set fileManager to current application’s NSFileManager’s defaultManager()
  
set aRes to (fileManager’s createDirectoryAtPath:dirPath withIntermediateDirectories:true attributes:(missing value) |error|:(reference))
  
set dtPath to dtPath & aUUID
  
  
tell application “Photos”
    set a to selection
    
if a = {} then return
    
set aRes to (export a to file dtPath)
  end tell
  
  
tell application “Finder”
    tell folder dtPath
      set fList to (every file) as alias list
    end tell
  end tell
  
  
if fList = {} then return false
  
return first item of fList
end exportSelectedPhotoOnPhotos

–CIFilterをかけたJPEG画像を生成
–参照:http://ashplanning.blogspot.jp/ のうちのどこか
on convAsFilteredJPEG(aPath, aFilterName)
  
  
–aliasをURL(input)とPOSIX path(output) に変換
  
set aURL to (current application’s |NSURL|’s fileURLWithPath:(POSIX path of aPath)) –Input
  
set aPOSIX to (POSIX path of aPath) & “_” & aFilterName & “.jpg” –Output
  
  
–CIImageを生成
  
set aCIImage to current application’s CIImage’s alloc()’s initWithContentsOfURL:aURL
  
  
– CIFilter をフィルタの名前で生成
  
set aFilter to current application’s CIFilter’s filterWithName:aFilterName
  
aFilter’s setDefaults() –各フィルタのパラメータはデフォルト
  
  
–Filterを実行
  
aFilter’s setValue:aCIImage forKey:“inputImage”
  
set aOutImage to aFilter’s valueForKey:“outputImage”
  
  
– NSBitmapImageRep を CIImage から生成
  
set aRep to current application’s NSBitmapImageRep’s alloc()’s initWithCIImage:aOutImage
  
  
– NSBitmapImageRep から JPEG データを取得
  
set jpegData to aRep’s representationUsingType:(current application’s NSJPEGFileType) |properties|:(missing value)
  
  
– ファイルに保存
  
set fsRes to jpegData’s writeToFile:aPOSIX atomically:true
  
if (fsRes as boolean) = false then return false –失敗した場合
  
return aPOSIX –成功した場合
  
end convAsFilteredJPEG

–数字のみかを調べて返す
on chkNumeric(checkString)
  set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:“0123456789″
  
set ret to my chkCompareString:checkString baseString:digitCharSet
  
return ret as boolean
end chkNumeric

on chkCompareString:checkString baseString:baseString
  set aScanner to current application’s NSScanner’s localizedScannerWithString:checkString
  
aScanner’s setCharactersToBeSkipped:(missing value)
  
aScanner’s scanCharactersFromSet:baseString intoString:(missing value)
  
return (aScanner’s isAtEnd()) as boolean
end chkCompareString:baseString:

–文字置換
on repChar(aStr, targStr, repStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set bString to aString’s stringByReplacingOccurrencesOfString:targStr withString:repStr
  
return bString as text
end repChar

–アルファベットと数字以外を削除して返す
on returnNumberAndAlphabetCharsOnly(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[^0-9A-Za-z]” withString:“” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
return anNSString as text
end returnNumberAndAlphabetCharsOnly

★Click Here to Open This Script 

2015/12/02 多言語OCR「FineReader OCR Pro」がAppStoreで40% OFF!

さんざん人前でデモをして他人にすすめまくっているMac用のAppleScript対応OCRソフトウェア「FineReader OCR Pro」が、Mac App Storeで40% Offの8,400円で販売中です(2015/12/02 本日まで)。

sales.png

けっこう頻繁にセール価格を設定するようなので、見るたびに価格が変わっていますが、この価格ならお買い得です(日付の書き方が英語圏の書き方のまんまなので、いろいろ誤解してました)。

finereaderocr.png

Mac App Store版のFineReaderを購入して気づいたことは・・・

・Downloadお試し版ではアプリケーション名が「FineReader」だったが、Mac App Store版では「FineReader OCR Pro」になっている(tell文を書き換える必要がある)

・現時点でのバージョン番号はv.12.1.4

・AppleScriptのコマンド「is sandboxed」で確認すると「true」が返る(DL版はfalseが返ってきていた)

finereader_sandbox.png

・文字認識を行うコマンドのパラメータ構成が変更になった(画像ファイルのオープンを別途行っておく必要アリ)ため、以前のバージョン用のAppleScriptがそのままでは構文確認(コンパイル)をパスしない。書き換える必要がある

・Mac App Store版ではSandbox化されており、Scriptから結果出力フォルダを指定しても無視され、出力フォルダに出力される。出力フォルダを求める命令が用意されており、is sandboxがtrueの場合には、そのフォルダを参照のこと

AppleScript名:FineReaderで出力フォルダを取得
tell application id “com.abbyy.FineReaderPro”
  set outDIr to (get output dir) as text
  
–> “Macintosh HD:Users:me:Library:Group Containers:XXXXXXXXXX.com.abbyy.FineReader12:tempGroupDir:scriptDir:”
end tell

★Click Here to Open This Script