Archive for the 'Script Libraries' Category

2017/09/06 ツイ4のページで新規連載マンガの画像を取得してPDF化(新規連載のPDF化)v3

Safariで表示中のWebマンガサイト「ツイ4」(更新情報をTwitterに投稿)のマンガを全エピソードダウンロードしてPDFにまとめるAppleScriptです。

実行にあたってはShane StanleyのAppleScript Libraries「BridgePlus」のインストールを必要とします(~/ibrary/Script Librariesフォルダに入れるだけ)。

実行開始時にはSafariでツイ4の特定のマンガのページをオープンしている必要があります。

tui4.png

Safariの最前面のウィンドウからURLやTitle、リンクされている画像の詳細情報を取得し、条件チェックなどを行なったのちに詳細なデータの抽出を行います。

次に、PDFの保存先を選択するダイアログを表示。このさい、デフォルトの保存先を「ピクチャ」フォルダ、ファイル名をマンガのタイトルに指定。

ページにリンクされていた画像(ツイではファイル名はシーケンシャル番号)から番号の情報だけを抽出して最大値、最小値を計算。この範囲で画像のダウンロード、PDFへの追記を行います。ただし、実運用してみたところ、Safariからすべての画像を取得できないようで(非同期表示しているようなので)、とりあえず1〜9999までの番号の画像を順次ダウンロードし、画像が存在しなければ処理を終了しています。

画像をダウンロードするたびにPDFに追記していますが、このあたりは途中でエラーが出て停止してもそれまでの処理内容が保存されることを意図してのことです。SSD搭載機では問題のない処理ですが、HDD搭載機では若干遅く感じるかもしれません(もはやHDD搭載機が身の回りにないので不明)。

これまでは、マンガの新規連載がはじまるとcurlコマンドで画像をダウンロードしてPDFに連結する作業を手で行なっていたのですが(誰も頼んでねえよ)、新規連載が増えたので自動化してみました。それでもありあわせの部品を組み合わせただけなので、それほど手間はかかっていません。

本Scriptとは別に更新された差分をPDFに連結するAppleScriptを作って日々実行し、大きな画面でブラウズするのに役立てています。割とこういう、ごくごく私的なScriptで野心的な処理を先行してテストしているものです。

AppleScript名:ツイ4のページで新規連載マンガの画像を取得してPDF化(新規連載のPDF化)v3
– Created 2016-09-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “QuartzCore”
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4808

property SMSForder : a reference to current application’s SMSForder
property |NSURL| : a reference to current application’s |NSURL|
property NSURLRequest : a reference to current application’s NSURLRequest
property NSURLConnection : a reference to current application’s NSURLConnection
property NSArray : a reference to current application’s NSArray
property NSFileManager : a reference to current application’s NSFileManager
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSPredicate : a reference to current application’s NSPredicate
property PDFPage : a reference to current application’s PDFPage
property PDFDocument : a reference to current application’s PDFDocument
property NSURLRequestUseProtocolCachePolicy : a reference to current application’s NSURLRequestUseProtocolCachePolicy
property NSNumberFormatterPadBeforePrefix : a reference to current application’s NSNumberFormatterPadBeforePrefix
property NSImage : a reference to current application’s NSImage
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSNumber : a reference to current application’s NSNumber
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

property theTargetSite : “http://sai-zen-sen.jp/”

tell application “Safari”
  if (count every document) = 0 then
    display notification “Safari does not open web page”
    
return
  end if
  
  
set docTitle to (do JavaScript “document.title” in front document) –Title
  
  
tell front document –URL
    set aURL to URL
  end tell
end tell

if aURL does not start with theTargetSite then
  display notification “This site is not the target”
  
return
end if

–Safariの最前面のウィンドウから画像リンクをすべて取得(Height, Width, URL)
set aList to getImageSizeAndURLOfFrontSafariDocument() of me

–取得した画像情報の2D Listをサイズで降順ソート
load framework –Force loading BridgePlus framework
set sortIndexes to {0, 1} –Key Item id: begin from 0
set sortOrders to {false, false}
set sortTypes to {“compare:”, “compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(aList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value))

–画像が取得できなかったら処理終了
if (resList as list) = {} then
  display notification “There is no images on this page”
  
return –No Result
end if

–最大サイズの画像情報を取得する(おそらくマンガ)
set {maxHeight, maxWidth, maxURL} to contents of first item of (resList as list)

set aNSURL to |NSURL|’s URLWithString:maxURL
set aNSURLfilename to (aNSURL’s lastPathComponent())
set aNSURLpure to aNSURL’s URLByDeletingLastPathComponent()
set aNSURLextension to aNSURLfilename’s pathExtension() as string
set aNSURLfilenameLen to (aNSURLfilename’s stringByDeletingPathExtension())’s |length|() as integer –画像ファイル名から拡張子を除去した部分の文字列長

–画像情報リストを画像サイズで抽出
set maxHeightStr to (maxHeight as integer) as string
set maxWidthStr to (maxWidth as integer) as string
set thePred to NSPredicate’s predicateWithFormat:(“(self[0] == “ & maxHeightStr & “) AND (self[1] == “ & maxWidthStr & “)”)
set bArray to (resList’s filteredArrayUsingPredicate:thePred) as list

–URLからファイル名の数値部分のみ抽出
set imageArray to current application’s NSMutableArray’s new()
repeat with i in bArray
  set j to contents of last item of i –(Image URL)
  
set aTmpURL to (|NSURL|’s URLWithString:j)
  
set aTmpfilename to (aTmpURL’s lastPathComponent()) as string
  
set numStr to first item of (my findPattern:(“^\\d{1,” & (aNSURLfilenameLen as string) & “}”) inString:aTmpfilename)
  
set jj2 to (SMSForder’s transformedFrom:numStr ICUTransform:“Fullwidth-Halfwidth” inverse:false) as integer
  (
imageArray’s addObject:jj2)
end repeat

–ファイル名から抽出した数値の最小値と最大値を求める。ただ、実運用したらWeb側から画像をすべて取得されない(非同期読み込みを行なっているらしい)ケースがあったため、ここの値は参考値程度にしか使えなかった
set maxRes to (imageArray’s valueForKeyPath:“@max.self”)’s intValue() –最大値
set minRes to (imageArray’s valueForKeyPath:“@min.self”)’s intValue() –最小値
log {minRes, maxRes}

–PDFのファイル名と場所をユーザーに確認
set pdfFile to (choose file name with prompt “Select PDF Name & Location” default location (path to pictures folder) default name (docTitle & “.pdf”))
set pdfFilePOSIX to POSIX path of pdfFile
set newFilePath to current application’s NSString’s stringWithString:pdfFilePOSIX

–Make Blank PDF
set aPDFdoc to PDFDocument’s alloc()’s init()

–Download each image and append to blank PDF
set insCount to 1 –画像ダウンロード用のページ数(Loop Counter)とPDF連結用のページ番号(insCount)を分離

–repeat with i from minRes as integer to maxRes as integer
repeat with i from 1 to 9999
  –URL部品の連結
  
set aFILENAME to numToZeroPaddingStr(i, aNSURLfilenameLen, “0″) of me
  
set aFULLURL to (aNSURLpure’s absoluteString() as string) & (aFILENAME as string) & “.” & (aNSURLextension as string)
  
set aURL to (|NSURL|’s URLWithString:aFULLURL)
  
  
–URL(画像)をダウンロード
  
set {uRes, headerRes, aData} to checkURLResourceExistence(aURL, 3) of me
  
  
if uRes = true then
    display notification “Episode “ & (i as string) & ” exists…”
    
set bImage to (NSImage’s alloc()’s initWithData:aData)
    (
aPDFdoc’s insertPage:(PDFPage’s alloc()’s initWithImage:bImage) atIndex:(insCount - 1))
    (
aPDFdoc’s writeToFile:newFilePath) –1Page更新するたびにファイル保存
    
set changedF to true –PDFにページが追記されたことを検出
  else
    display notification “No more new episode….”
    
exit repeat
  end if
  
  
set insCount to insCount + 1
end repeat

–FinderコメントにURLを記入
tell application “Finder”
  set comment of (pdfFile as alias) to (aNSURLpure’s absoluteString() as string)
end tell

–生成したPDFをオープン。ビューワー経由ではなくFinder経由でopen命令を送って表示
tell application “Finder”
  open (pdfFile as alias)
end tell
–ここで処理終了

—————

on getImageSizeAndURLOfFrontSafariDocument()
  set aList to {}
  
  
tell application “Safari”
    if its running then
      if (count every document) = 0 then return {}
      
set aRes to (do JavaScript “document.images.length” in front document)
      
      
repeat with i from 0 to (aRes - 1)
        set aHeight to do JavaScript ((“document.images[” & i as string) & “].height”) in front document
        
set aWidth to do JavaScript ((“document.images[” & i as string) & “].width”) in front document
        
set aSRC to do JavaScript ((“document.images[” & i as string) & “].src”) in front document
        
set the end of aList to {aHeight, aWidth, aSRC}
      end repeat
    end if
  end tell
  
  
return aList
end getImageSizeAndURLOfFrontSafariDocument

on findPattern:thePattern inString:theString
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list
  
set theResult to {}
  
set theNSString to NSString’s stringWithString:theString
  
  
repeat with i in theFinds
    set theRange to (contents of i)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

–1D List(文字)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DList:theList ascOrder:aBool
  set aDdesc to NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

–整数の値に指定桁数ゼロパディングして文字列で返す
on numToZeroPaddingStr(aNum as integer, aDigit as integer, paddingChar as text)
  set aNumForm to NSNumberFormatter’s alloc()’s init()
  
aNumForm’s setPaddingPosition:(NSNumberFormatterPadBeforePrefix)
  
aNumForm’s setPaddingCharacter:paddingChar
  
aNumForm’s setMinimumIntegerDigits:aDigit
  
  
set bNum to NSNumber’s numberWithInt:aNum
  
set aStr to aNumForm’s stringFromNumber:bNum
  
  
return aStr as text
end numToZeroPaddingStr

– 指定URLにファイル(画像など)が存在するかチェック
–> {存在確認結果(boolean), レスポンスヘッダー(NSDictionary), データ(NSData)}
on checkURLResourceExistence(aURL, timeOutSec as real)
  set aRequest to (NSURLRequest’s requestWithURL:aURL cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:timeOutSec)
  
set aRes to (NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value))
  
set dRes to (first item of (aRes as list))
  
set bRes to (second item of (aRes as list))
  
if bRes is not equal to missing value then
    set hRes to (bRes’s allHeaderFields())
    
set aResCode to (bRes’s statusCode()) as integer
  else
    set hRes to {}
    
set aResCode to 404
  end if
  
return {(aResCode = 200), hRes, dRes}
end checkURLResourceExistence

–指定PDFのページ数をかぞえる(10.9対応。普通にPDFpageから取得)
–返り値:PDFファイルのページ数(整数値)
on pdfPageCount(aFile)
  set aFile to POSIX path of aFile
  
set theURL to |NSURL|’s fileURLWithPath:aFile
  
set aPDFdoc to PDFDocument’s alloc()’s initWithURL:theURL
  
set aRes to aPDFdoc’s pageCount()
  
return aRes as integer
end pdfPageCount

★Click Here to Open This Script 

2017/09/05 指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める

ファイル名の先頭に1〜3桁の数字がついているテキストファイルに対して、指定フォルダ以下のものをすべて取得し、これらの数(連番)に抜けがないかをチェックするAppleScriptです。

filenames2.png

指定フォルダ以下に存在するテキストファイルをSpotlightで検索し、すべてのファイル名を取得してそこから先頭に存在する1〜3桁の数字を取得。全角文字が混入している場合にそなえて数字をすべて全角→半角変換。これらの数(おそらく連番)で途中に抜けがないかチェックします。

実行にはShane StanleyのAppleScript Libraries「BridgePlus」「MetaData Lib」のインストールが必要です。

連番リストの作成や全角→半角変換、Spotlight検索などをScript Librariesの機能に依存して書いています。また、集合(NSMutableSet)を扱えることで非常に高度な処理を完結に記述できています。処理速度も非常に高速です。

 127ファイルの連番チェック処理:0.113457024097 sec.
 449ファイルの連番チェック処理:0.381642997265 sec.

(結果はMacBook Pro Retina 2012 Core i7 2.66GHz+8GBでの所用時間)

AppleScript名:指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める
– Created 2017-09-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
use bPlus : script “BridgePlus”
–http://piyocast.com/as/archives/4803

property SMSForder : a reference to current application’s SMSForder
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableSet : a reference to current application’s NSMutableSet
property NSIndexSet : a reference to current application’s NSIndexSet
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

load framework –BridgePlus’s force framework loading command

–選択したフォルダ以下のPlain TextをすべてSpotlightで求める(POSIX path list)
set theFolder to (choose folder)
set aRes to retMissingNumberFromEachFiles(theFolder, {“public.plain-text”}) of me
–>  {}

on retMissingNumberFromEachFiles(theFolder, fileTypeList)
  set theFiles to mdLib’s searchFolders:{theFolder} searchString:“kMDItemContentType IN[c] %@” searchArgs:fileTypeList
  
if theFiles = {} then return
  
  
–取得したPOSIX Pathのリストからファイル名の部分のみ抽出
  
set anArray to NSMutableArray’s arrayWithArray:theFiles
  
set bArray to (anArray’s valueForKeyPath:“lastPathComponent”) as list
  
  
–各ファイルの名称の冒頭から1〜3桁 の数字を取り出して、全角–>半角変換を行いつつリストに追加
  
set nArray to NSMutableArray’s new()
  
repeat with i in bArray
    set j to contents of i
    
set aRes to (my findPattern:“^\\d{1,3}” inString:j)
    
if aRes is not equal to {} then
      set jj to (contents of first item of aRes) as string
      
set jj2 to (SMSForder’s transformedFrom:jj ICUTransform:“Fullwidth-Halfwidth” inverse:false) as integer
      (
nArray’s addObject:jj2)
    end if
  end repeat
  
  
–最大値、最小値をもとに連番リストを作成し、ファイル名から得られた配列データとの補集合を求める
  
set maxRes to (nArray’s valueForKeyPath:“@max.self”)’s intValue()
  
set minRes to (nArray’s valueForKeyPath:“@min.self”)’s intValue()
  
  
–最小値から最大値までの連番リスト作成
  
set theIndexSet to NSIndexSet’s indexSetWithIndexesInRange:{minRes, maxRes}
  
set theList to (SMSForder’s arrayWithIndexSet:theIndexSet) as list
  
  
set aSet to NSMutableSet’s setWithArray:theList
  
set bSet to NSMutableSet’s setWithArray:nArray
  
aSet’s minusSet:bSet –補集合
  
  
return (aSet’s allObjects() as list)
  
end retMissingNumberFromEachFiles

on findPattern:thePattern inString:theString
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list
  
set theResult to {}
  
set theNSString to NSString’s stringWithString:theString
  
  
repeat with i in theFinds
    set theRange to (contents of i)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

★Click Here to Open This Script 

2017/08/23 指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存

Localの指定フォルダ以下にあるHTMLをすべてSpotlightで抽出し、見つかったすべてのHTMLのテキストエンコーディングをUTF-8に変更して上書き保存するAppleScriptです。

本Script単体では動作確認できないので、確認のためにアプレットを用意しておきました。HTMLへの要素アクセスにはオープンソースのHTMLReader.framework(By Nolan Waite)を、Spotlight検索にはShane Stanleyの「Metadata Lib」を、日本語テキストファイルのエンコーディング自動検出には自作の「japaneseTextEncodingDetector」を用いています。これらをすべて含んだアプレット「htmlutf8rewriter.app」を用意しておきました(Code Signずみ)。

–> Download htmlutf8rewriter.zip

実際にテストデータ(7,979 files)で実験してみたところ、MacBook Air 2011(Core i5, 1.6GHz)上で702 Seconds、12分で処理できました(macOS 10.13beta 7で実行)。

バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011でこの程度の速度なので、手元のMacBook Pro 2012で実行すると半分ぐらいの時間で実行できるものと思われます。

Dual Core(4 thread)のMacBook Airでも1〜2thread分は処理に余裕があったので、よりコア数の多いMac上で実行する場合には並列実行するとマシンパワーを絞り出せるものと思われます。

用意したテストデータには行儀の悪いものも混在していたので、300程度のエラーが発生。こうしたエラーデータに対しては、別途何らかのテキストエディタをコントロールして書き換え&保存を行なってもよいのかもしれません。

最初からテキストエディタをコントロールして書き換えを行うと並列処理できないですし、AppleScript単体で実行するよりも処理時間がかかります(現状だと秒間11ファイルぐらい処理できていますが、外部のテキストエディタを制御して処理すると秒間2ファイルぐらいまで落ちるはずです)。

AppleScript名:指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存
– Created 2017-08-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “HTMLReader” –https://github.com/nolanw/HTMLReader
use jLib : script “japaneseTextEncodingDetector”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4789

property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property HTMLDocument : a reference to current application’s HTMLDocument

set theFolder to choose folder
set theRecord to mdLib’s searchFolders:{theFolder} searchString:“kMDItemFSName ENDSWITH[c] ’.html’” searchArgs:{}
set aCount to 1

repeat with i in theRecord
  set hRes to overWriteHTMLWithUTF8Encoding(i) of me
  
if hRes = true then
    set aCount to aCount + 1
  end if
end repeat

return aCount

on overWriteHTMLWithUTF8Encoding(aPOSIX)
  set aRes to readJapanesTextFileWithGuessingEncoding(aPOSIX) of jLib
  
set bRes to retSpecifiedElementsFromHTMLString(aRes, “meta”) of me
  
–>  {(HTMLElement) 0 children>, (HTMLElement) 0 children>, ….
  
  
–Search Element
  
set aCount to 0
  
set hitF to false
  
repeat with i in bRes
    set tRes to i’s attributes()
    
–> (NSDictionary) {http-equiv:”Content-Type”, content:”text/html; charset=euc-jp”}
    
    
set kList to tRes’s allKeys() as list
    
–> {”http-equiv”, “content”}
    
    
if kList contains “http-equiv” then
      set hitF to true
      
exit repeat
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
if hitF = false then return false –Element Not Found
  
  
–Text Encoding を書き換えたHTMLElementを作成する
  
set aHTML to HTMLDocument’s documentWithString:aRes
  
set anElement to (aHTML’s nodesMatchingSelector:“meta”)’s objectAtIndex:aCount
  
anElement’s setObject:“text/html; charset=UTF-8″ forKeyedSubscript:“content”
  
set aRootHTMLStr to anElement’s |document|()’s serializedFragment()
  
  
–Overwrite HTML
  
set aRes to (aRootHTMLStr’s writeToFile:aPOSIX atomically:false encoding:(NSUTF8StringEncoding) |error|:(missing value)) as boolean
  
return aRes
end overWriteHTMLWithUTF8Encoding

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementsFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)
  
return anElement as list
end retSpecifiedElementsFromHTMLString

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromHTMLString

–指定されたローカルのHTMLファイルを、指定文字エンコーディングで読み込んで、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromLocalHTML(aPOSIX, anEncoding, aTag)
  set aPath to NSString’s stringWithString:aPOSIX
  
set aData to NSString’s stringWithContentsOfFile:aPath encoding:(anEncoding) |error|:(missing value)
  
if aData = missing value then return false
  
  
set aHTML to HTMLDocument’s documentWithString:aData
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromLocalHTML

★Click Here to Open This Script 

2017/08/08 Metadata Lib 1.0

Spotlightの仕組みを用いてファイル検索を行うAppleScript Libraries「Metadata Lib」(By Shane Stanley)が公開されました。macOS 10.9以降の環境で利用できます。

Metadata Libのアーカイブをダウンロードしたのちに展開して、~/Library/Script Libraries/フォルダに「Metadata Lib.scptd」ファイルを入れればインストールは完了です。

とくにsdefでAppleScript用語辞書が定義されておらず、ソースもすべて読めるようになっています。ただし、各サブルーチンのハンドラの記法はAppleScriptネイティブの、

 someHander(aParamerer)

ではなく、Objective-C風(AppleScriptObjC風)の、

 someHander:aParameter

となっている点に注意が必要です。

内容に特殊なものはないので、「だいたいこんなかんじだよね〜」という軽いライブラリです。いきなり難解な内容にのたうちまわりながら苦しむといったことはまったくありません。今回のリリースはFirst Releaseですが、次あたりでAppleScript用語辞書をつけてきそうな雰囲気はあります。

mdls系コマンド

ファイルのメタデータを調べるにはシェルの「mdls」コマンドを使いますが、それと同じ内容を得られます。ただし、レコード中にオブジェクトを入れて返してくるので、mdfindよりも高速とのことです(Shane本人から念押し)。

use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″

set theFile to choose file
set theRecord to mdLib’s fetchMetadataFor:theFile

利用するだけれあれば、とくにAppleScriptObjCやCocoaの知識は必要とされません。

mdfind系コマンド

ファイルのメタデータ検索を行うにはシェルの「mdfind」コマンドを使いますが、だいたい同じです。本ライブラリで特徴的なのはNSPredicateで指定するPredicate文を記述して検索条件を指定する点にあります(自分はPredicate文は隠蔽するタチなので)。

また、mdfindよりも高速に検索が行える(Shane本人より念押し)ことに特徴があります。これは、mdfindがPOSIX Pathをテキストで返してくるのでAppleScript側で行単位にparseしつつPOSIX path経由でaliasに変換するような「データ変換」とshell呼び出しのオーバーヘッドの部分を指しているものと思われます(以前、mdfindとCocoa呼び出しで速度比較して、10%ぐらいCocoa呼び出しのほうが高速でした)。

そのため、本ライブラリでこの手のコマンドを利用するにはAppleScriptObjCやCocoaの知識が若干必要です。

もし、極力Cocoaの知識を使わずに本ライブラリによるメタデータ検索を行いたい場合には、あらかじめ検索パターンをFinder上で保存しておけば、searchStringsFromSavedSearch:ハンドラで保存しておいた検索パターンを指定して(Predicate文などを書かずに)、メタデータ検索を行うことも可能です(Shane本人より念押し)。

ひととおりライブラリの中身を見て回った感想は、Metadata検索系の機能だけでなく、ファイル検索系の他の機能も一緒にまとめてみては? というものでした。つまり、個人的にはぜんぜん使っていないFinder Tagとか、よく使っているFinder Labelによる検索とか、NSFileManagerによる再帰検索(Spotlightが効かない状態でも有効)など、バラバラに存在しているものを統合するといいんじゃないかと思えてきました。

どうなんでしょう?

2017/08/02 指定のアプリケーションのURL Schemeを取得する

指定のアプリケーションに設定してあるURL Schemeを取得するAppleScriptです。

指定のアプリケーションバンドル内のInfo.plistにあるCFBundleURLSchemesにアクセスして、カスタムURLプロトコルスキームを取得します。実行にはShane Stanleyの「Bridge Plus」AppleScriptライブラリを~/Library/Script Librariesフォルダにインストールしておくことが必要です。

たとえば、写真.app(Photos.app)であれば「photos://」というカスタムURLプロトコルが定義されており、本Scriptを実行して写真.app(Photos.app)を選択すると、

–> {appName:”Photos”, appBundleID:”com.apple.Photos”, urlScheme:{”photos”}}

という結果が返ってきます。

実際に調べてみると、OSのメジャーアップデート時に予想外のアプリケーションにカスタムURLスキームが新設されていることがあります。そういう調査のために作成したものです。

もちろん、1つのアプリケーションをいちいちchoose fileで選択して調べるとかそういうことではなく、指定フォルダ以下のアプリケーションをすべて取得して、それらのアプリケーションすべてのURL Schemeを調査するScriptのための部品です。手作業で1つ1つ調べるなんて、意味のないことです。

AppleScript名:指定のアプリケーションのURL Schemeを取得する
– Created 2017-07-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4759

load framework

set aP to choose file
set aURLrec to getAppURLSchemes(aP) of me
–> {appName:”Photos”, appBundleID:”com.apple.Photos”, urlScheme:{”photos”}}

on getAppURLSchemes(aP)
  set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of aP)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
set aDict to aBundle’s infoDictionary()
  
  
set appNameDat to (aDict’s valueForKey:“CFBundleName”) as string
  
set bundleIDat to (aDict’s valueForKey:“CFBundleIdentifier”) as string
  
  
set urlSchemes to (aDict’s valueForKey:“CFBundleURLTypes”)
  
if urlSchemes is not equal to missing value then
    set urlList to urlSchemes’s valueForKey:“CFBundleURLSchemes”
    
set urlListFlat to (current application’s SMSForder’s arrayByFlattening:urlList) as list
  else
    set urlListFlat to {}
  end if
  
  
set aRec to {appName:appNameDat, appBundleID:bundleIDat, urlScheme:urlListFlat}
  
return aRec
end getAppURLSchemes

★Click Here to Open This Script 

2017/07/19 SQL LibでmacOS上の各種SQLite DBの情報を確認する

Shane Stanleyの「SQL Lib」を用いてmacOS上のアプリケーションが使用しているSQLite DBの内容を確認してみるAppleScriptです。

このところSQL Libを用いて、いろいろと基礎的な操作を試しています。Shane Stanleyから「基礎的な操作ってどんなんだよ?!」とツッコミが入っていろいろやりとりをしつつ、自分でも試していたら急に全体像がくわしく見えてきました。Google翻訳の精度が上がって、わりと日本語の文章でも微妙なニュアンスが伝わるようで、チョットコワイデス(^ー^;;

SQL Libについては、AppleScriptのレベルで日常的な検索や更新、作成や削除などはひととおりできるようで、少し込み入った操作や取得になると内蔵のFMDBASフレームワークに(Cocoaのオブジェクトを経由して)アクセスすることになるようです。

なので、SQLiteのバージョン取得などもSQL Libに通常のAppleScriptのハンドラ呼び出しで行うのではなく、内蔵のFMDBASフレームワークに問い合わせを行うことになるのだとか(do shell scriptコマンド経由でもいいんですが)。

macOS上のアプリケーションの多くはSQLite DBを利用しており、直接データを読めると便利なケースもありそうです。Notes、Safari、Calendarが有名ですが、ほかにもあるでしょう(探せばきっといっぱいある)。もしも、Mail.appもSQLiteを使っているのであればSQLite経由でデータを取れたほうが便利なケースもありそうです。SpotlightのIndex DBとかも。

AppleScriptなどの言語を使っているユーザーはSQLと相性がいいとか悪いとかいう話をすれば、きっと「関係ない」と思っている人が多いことでしょう。ただ、SQLiteの形式になっている既存のデータであるとか、巨大なデータ配布物に問い合わせを行う場合にはどうしても避けて通れない存在であるため、なるべくフレンドリーなインタフェース経由で操作したいところです。

AppleScriptでも巨大なデータ(10万レコード以上のrecordとかlist)を扱うようになってくると、やはりデータベースの併用を視野に入れる必要が出てきます。FileMaker ProはものすごくAppleScriptにフレンドリーな存在ですが、SQLiteならOS標準装備なうえにFileMaker Proよりもベンチマーク上では倍ぐらいのパフォーマンスが出るようなので、使わない手はないことでしょう。

とりあえず、指定データベース上のテーブル名の取得と、テーブルのスキーマ定義の取得のあたりを(Shaneに聞きつつ)まとめてみました。各アプリケーションが利用しているSQLite DBのスキーマについてはOSのアップデートなどで変更になる場合もありそうですから、呼び出す前にスキーマの照合ぐらいはしておいたほうがよさそうだと思ったもので。

AppleScript名:SQL LibでmacOS上の各種SQLite DBの情報を確認する
– Created 2017-07-18 by Shane Stanley
– Modified 2017-07-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use sqlLib : script “SQLite Lib” version “1.0.0″
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4731

–Notes
set aDBpath to (POSIX path of (path to library folder from user domain)) & “Containers/com.apple.Notes/Data/Library/Notes/NotesV6.storedata”
set db1Res to retTableNames(aDBpath) of me
–>  {”ZATTACHMENT”, “ZNOTEBODY”, “ZOFFLINEACTION”, “Z_PRIMARYKEY”, “Z_METADATA”, “ZFOLDER”, “Z_MODELCACHE”, “ZACCOUNT”, “ZNOTE”}

–Safari
set bDBpath to (POSIX path of (path to library folder from user domain)) & “Safari/History.db”
set db2Res to retTableNames(bDBpath) of me
–>  {”history_items”, “sqlite_sequence”, “history_visits”, “history_tombstones”, “metadata”, “history_client_versions”}

–Calendar
set cDBpath to (POSIX path of (path to library folder from user domain)) & “Calendars/Calendar Cache”
set db3Res to retTableNames(cDBpath) of me
–> {”ZATTACHMENT”, “ZATTENDEE”, “ZCHANGEREQUESTDEPENDENCY”, “ZCOMMENT”, “ZDIFF”, “ZERROR”, “ZICSELEMENTPROPERTIES”, “ZLOCATION”, “ZMESSAGECONTENTS”, “ZPERSISTENTOPERATION”, “ZRECURRENCEEXCEPTION”, “ZRECURRENCESET”, “ZSEARCHPROPERTY”, “Z_METADATA”, “Z_MODELCACHE”, “ZCALENDARITEM”, “ZNODE”, “ZSUBSCRIPTIONINFO”, “ZALARM”, “ZCALENDARUSERADDRESS”, “ZCHANGEREQUEST”, “ZDEFAULTALARMSET”, “ZMESSAGE”, “ZPUBLICATION”, “ZSHAREE”, “Z_PRIMARYKEY”}

–Get Schema Definition of each Table(Safari Histrory)
repeat with i in db2Res
  set aRes to retDBSChema(bDBpath, i)
  
log aRes
  
–> {{sql:”CREATE TABLE history_client_versions (client_version INTEGER PRIMARY KEY,last_seen REAL NOT NULL)”, rootpage:540, type:”table”, name:”history_client_versions”, tbl_name:”history_client_versions”}, …..
  
end repeat

on retTableNames(aDBpath)
  set theDb to sqlLib’s makeNewDbWith:aDBpath
  
theDb’s openReadOnly()
  
set aRes to theDb’s doQuery:“select name from sqlite_master where type = ’table’;”
  
set bRes to (current application’s SMSForder’s arrayByFlattening:aRes) as list
  
theDb’s |close|()
  
return bRes
end retTableNames

on retDBSChema(aDBpath, aTableName)
  set theDb to sqlLib’s makeNewDbWith:aDBpath
  
theDb’s openReadOnly()
  
  
set x to theDb’s underlyingFMDatabase()’s getSchema()
  
set theResult to current application’s NSMutableArray’s array()
  
  
repeat while x’s next() as boolean
    theResult’s addObject:(x’s resultDictionary())
  end repeat
  
  
theDb’s |close|()
  
return theResult as list
end retDBSChema

★Click Here to Open This Script 

2017/02/23 Myriad Tables v1.0.7がリリースされる

Shane StanleyによるAppleScript Libraries「Myriad Tables Lib」のバージョン1.0.7がリリースされました。AppleScriptから手軽に「表」インタフェースを利用可能にするものです。

table3.png

同ライブラリをサイトからダウンロードし、~/Libraries/Script Librariesフォルダに入れるとAppleScriptから使えるようになります(このフォルダが存在しない場合には作成)。

tables01.png

tables02.png

同ライブラリにはAppleScript用語辞書が用意されており、Script Editorに直接ドラッグ&ドロップではなく、いったんScript Editorの「ライブラリ」ウィンドウに登録し、リスト上の「Myriad Tables Lib」を選択した状態で「用語説明を開く」をクリックすると、用語辞書の内容を確認できます。

tables003.png

tables004_resized.png

一般的に、Excel書類やCSVなどで支給されたデータに対して、どの行のデータを処理するかをセルの選択範囲で明示的にScriptに対して指示することはよくあります。ただ、実行環境にMicrosoft Excelがすべてインストールされているわけでもありません。

Myriad Tables Libがあれば、CSVデータ中の処理範囲を選択したり、簡易的なデータ入力・確認用のGUIをAppleScriptから利用できます。

Myriad Tables Lib 1.0.7の新機能として紹介されているものを、同ライブラリ添付のサンプルコードから紹介してみましょう。

新機能:セルのダブルクリックを「OK」ボタンのクリックと等価とみなす「double click means OK」オプション

table1.png

AppleScript名:double click means OK
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
use script “Myriad Tables Lib” version “1.0.7″

– This table shows how to use the ’double click means OK’ parameter
display table with data {“One”, “Two”, “Three”, “Four”, “Five”} with title “Simple table” with prompt “You can double-click an entry rather than selecting and pressing OK” with double click means OK and empty selection allowed

–> {rows selected:{5}, values selected:{”Five”}, values returned:{”One”, “Two”, “Three”, “Four”, “Five”}, button number:1, timed out:false, final position:{978.0, 254.0, 246.0, 278.0}}

★Click Here to Open This Script 

新機能:ダイアログの表示座標を指定する「initial position」オプション

table2.png

AppleScript名:initial position
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
use script “Myriad Tables Lib” version “1.0.7″

– This table shows the use of the ’initial position’ parameter to set the dialog’s position
set theTable to make new table with data {{1.0, 1, 1.11, 1}, {-2.0, 2, 2.2, 2}, {3.11, -3, 3.11, 3}, {4.0, 4, -4.41, 4}, {5.0, 5, 5.55, -5}} column headings {“Reals”, “Integers”, “Reals”, “Integers”} with prompt “This table appears at the top-left of the screen” with double click means OK and empty selection allowed
modify table theTable initial position {0, 0} with alternate backgrounds
display table theTable

★Click Here to Open This Script 

initial positionでは画面上の表示座標だけでなく、サイズ{表示位置(X), 表示位置(Y), 表示幅(X),表示高さ(Y)}の指定も可能です。

AppleScript名:initial position_2
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
use script “Myriad Tables Lib” version “1.0.7″

– This table shows the use of the ’initial position’ parameter to set the dialog’s size and position
set theTable to make new table with data {{1.0, 1, 1.11, 1}, {-2.0, 2, 2.2, 2}, {3.11, -3, 3.11, 3}, {4.0, 4, -4.41, 4}, {5.0, 5, 5.55, -5}} column headings {“Reals”, “Integers”, “Reals”, “Integers”} with prompt “This table’s size and position have been set in the script” with double click means OK and empty selection allowed
modify table theTable initial position {100, 10, 400, 400} with alternate backgrounds
display table theTable

★Click Here to Open This Script 

新機能:ダイアログの前回表示位置&サイズを取得する「final position」

AppleScript名:final position
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
use script “Myriad Tables Lib” version “1.0.7″

– This table shows how to retrieve the final position and size of the dialog
set theTable to make new table with data {{1.0, 1, 1.11, 1}, {-2.0, 2, 2.2, 2}, {3.11, -3, 3.11, 3}, {4.0, 4, -4.41, 4}, {5.0, 5, 5.55, -5}} column headings {“Reals”, “Integers”, “Reals”, “Integers”} with prompt “Move and resize this dialog before clicking OK” with double click means OK and empty selection allowed
modify table theTable with alternate backgrounds
set theResult to display table theTable
set theBounds to final position of theResult

– This table is positioned using the results from the previous one
set theTable to make new table with data {{1.0, 1, 1.11, 1}, {-2.0, 2, 2.2, 2}, {3.11, -3, 3.11, 3}, {4.0, 4, -4.41, 4}, {5.0, 5, 5.55, -5}} column headings {“Reals”, “Integers”, “Reals”, “Integers”} with prompt “This table’s size and position should match those used last time” with double click means OK and empty selection allowed
modify table theTable initial position theBounds with alternate backgrounds
display table theTable

★Click Here to Open This Script 

2016/12/15 PDFしおり用データをNumbersから取得

PDFに「しおり」を作成する元のデータをNumbers上に記述しておくと、作成用のデータを取得・変換するAppleScriptです。構文確認および実行には、Shane Stanleyの「BridgePlus」AppleScript Libraries(フリー)のインストールを必要とします。

また、Numbersで(↓)のような書類を作成して、Numbersでオープンしていることが動作の前提条件です。

numbers_shiori.png

元のプログラムでは直接Script Editor上でレコードとして記述するのが、なかなか大変。また、親項目をタイトル文字列で記述するのも(作業時にミスりそうで)大変だったので、Numbers書類上で記述できるようにしてみたものです。

shiori.png

親項目は番号で記述するようにして、ID自体の連番の生成もAppleScriptから行い、極力作業ミスが発生しないように配慮してみました。

shiori2.png

AppleScript名:しおり用データをNumbersから取得
【コメント】 Book2_index_v2 を前提としています
– Created 2016-12-15 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4363

set aData to getIndexRecListFromNumbers() of me
–> {{|index|:3, title:”広告”, |parent|:”"}, {|index|:4, title:”本書購入特典のご案内”, |parent|:”"},…..

–NumbersのデータからPDFに付けるしおりのデータを取得する
on getIndexRecListFromNumbers()
  tell application “Numbers”
    tell window 1
      set aWinProp to properties
    end tell
    
    
set aDoc to document of aWinProp
    
tell aDoc
      tell active sheet
        tell table 1
          set colNum to column count
          
if colNum is not equal to 4 then error “Illegal Column Numbers”
          
set rowNum to row count
          
set vList to value of every cell
        end tell
      end tell
    end tell
  end tell
  
  
–Transform 1D array to 2D array
  
load framework
  
set tdList to (current application’s SMSForder’s subarraysFrom:(vList) groupedBy:colNum |error|:(missing value)) as list
  
–> {{”ID”, “index”, “title”, “parent”}, {1.0, 3.0, “広告”, missing value}, …..
  
  
–Skip First Row
  
set td2List to rest of tdList –first itemだけスキップする
  
  
set mokujiRecords to {}
  
repeat with i in td2List
    copy i to {anID, anIND, aTITLE, aParent}
    
    
–log {anID, anIND, aTITLE, aParent}
    
if aParent is not equal to missing value then
      set bParent to contents of item 3 of (item aParent of td2List)
    else
      set bParent to “”
    end if
    
    
set tmpRec to {|index|:(contents of anIND) as integer, title:aTITLE, |parent|:bParent}
    
set the end of mokujiRecords to tmpRec
  end repeat
  
  
return mokujiRecords
  
end getIndexRecListFromNumbers

★Click Here to Open This Script 

2016/11/18 ライブラリを使って指定カレンダー(iCloud)の指定日のイベントを削除する

Shane StanleyのAppleScriptライブラリ「Calendar Lib」を利用して、EventKit経由で指定カレンダーの指定日時のイベントを削除するAppleScriptです。

構文確認(コンパイル)および実行のためには、Shane Stanleyの「CalendarLib」を~/Library/Script Librariesフォルダにインストールしておくことが必要です(作成時にはバージョン1.1.2を使用)。

昨日のCalendar.appを操作するScriptがあまりに実用的でなかったため、ライブラリを使ってOSのAPIを直接呼んでみることにしました。あらかじめ、できることはわかっていたものの、ドキュメント中に削除のサンプルが存在しておらず、ライブラリ内を調査してイベントの削除方法をみつけました。

本Script実行時にCalendar.appを起動していると、実行後3秒程度でCalendar.appの画面側でもイベントが削除されたことが確認できました。

Calendar Libではカレンダー種別を cal local/ cal cloud/ cal exchange/ cal subscriptionと明示的に指定できるため、Calendar.appを直接操作するよりも(はるかに)便利です。

AppleScript名:ライブラリを使って指定カレンダー(iCloud)の指定日のイベントを削除する
– Created 2016-11-18 17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use calLib : script “CalendarLib EC”
–http://piyocast.com/as/archives/4322

set sDat to “2016/11/21 0:00:00″
set eDat to “2016/11/22 0:00:00″
set sDatO to date sDat
set eDatO to date eDat

set theStore to fetch store
set theCal to fetch calendar “ぴよまるソフトウェア” cal type cal cloud event store theStore – change to suit

set theEvents to fetch events starting date sDatO ending date eDatO searching cals {theCal} event store theStore

repeat with i in theEvents
  set j to contents of i
  
remove event event j event store theStore without future events
end repeat

★Click Here to Open This Script 

2016/10/25 指定ムービーを指定形式に変換する v2

指定のムービーファイルを指定形式に変換するAppleScriptの改良版です。

一晩たったら、旧石器時代から現代までタイムスリップしたぐらいの改良が加わっています(汗)。野蛮な手段でファイル拡張子からUTIを求めていたのが、フレームワーク呼び出し一発で、、、処理速度も大幅に向上しています。

AppleScript名:指定ムービーを指定形式に変換する v2
– Created 2016-10-24 by Shane Stanley
– Modified 2016-10-24 by Takaaki Naganoya
– Modified 2016-10-25 by Shane Stanley
use AppleScript version “2.4″
use framework “Foundation”
use framework “AVFoundation”
use scripting additions
use BridgePlus : script “BridgePlus” –Version 1.3.2以降

load framework
–http://piyocast.com/as/archives/4288

set posixPath to POSIX path of (choose file with prompt “Choose a Movie file:”)
my convertMovieAt:posixPath ToType:“AVAssetExportPresetAppleM4A” withExtension:“m4a” deleteOriginal:false

–指定のムービーを、指定の書き出しプリセットで、指定のファイル形式で、オリジナルファイルを削除するかどうか指定しつつ書き出し
on convertMovieAt:(posixPath as string) ToType:(assetTypeStr as string) withExtension:(aExt as string) deleteOriginal:(deleteFlag as boolean)
  set theURL to current application’s |NSURL|’s fileURLWithPath:posixPath
  
  
set aPreset to current application’s NSString’s stringWithString:assetTypeStr
  
  
– set destination to use same path, different extension
  
set destURL to theURL’s URLByDeletingPathExtension()’s URLByAppendingPathExtension:aExt
  
set theAsset to current application’s AVAsset’s assetWithURL:theURL
  
  
– check asset can be converted
  
set allowedPresets to current application’s AVAssetExportSession’s exportPresetsCompatibleWithAsset:theAsset
  
if (allowedPresets’s containsObject:aPreset) as boolean is false then
    error “Can’t export this file as an .” & aExt & ” file.”
  end if
  
  
– set up export session
  
set fileTypeUTIstr to retFileFormatUTI(aExt) of me –ファイル拡張子からFile Type UTIを求める
  
set theSession to current application’s AVAssetExportSession’s exportSessionWithAsset:theAsset presetName:aPreset
  
theSession’s setOutputFileType:fileTypeUTIstr
  
theSession’s setOutputURL:destURL
  
  
– begin export and poll for completion
  
theSession’s exportAsynchronouslyWithCompletionHandler:(missing value)
  
repeat
    set theStatus to theSession’s status() as integer
    
if theStatus < 3 then
      delay 0.2
    else
      exit repeat
    end if
  end repeat
  
  
– throw error if it failed
  
if theStatus = (current application’s AVAssetExportSessionStatusFailed) as integer then
    error (theSession’s |error|()’s localizedDescription() as text)
  end if
  
  
– delete original if required
  
if deleteFlag then
    current application’s NSFileManager’s defaultManager()’s removeItemAtURL:theURL |error|:(missing value)
  end if
end convertMovieAt:ToType:withExtension:deleteOriginal:

on retFileFormatUTI(aExt as string)
  return (current application’s SMSForder’s UTIForExtension:aExt)
end retFileFormatUTI

★Click Here to Open This Script 

2016/10/25 ムービー系の拡張子からFile Format UTIを取得する v2, v3

ムービー系のファイルの拡張子から、File Format UTIを取得するAppleScriptの改良版です。

Shane Stanleyから指摘があって、

(1)com.apple.quicktime-movieと current application’s AVFileTypeQuickTimeMovieは同じものなので変換する必要はないよ(なんとなく、そうじゃないかとは思ってました ^ー^;;)

(2)BridgePlus v1.3.2に拡張子からUTIを求めるメソッドが用意してあるよ(!!!)

というわけで、(1)を反映させたv2、(2)まで反映させたv3を作成してみましたが、v3にいたってはたったの1行。どこかにもっとスマートな解決策が転がっていると思っていましたが、ここまでスマートになるとは(^ー^;;;

一応、試した範囲ではv1もv2もv3も実行結果は同じです。

AppleScript名:ムービー系の拡張子からFile Format UTIを取得する v2
– Created 2016-10-24 by Takaaki Naganoya
– Modified 2016-10-25 by Takaaki Naganoya
– Modified 2016-10-25 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use framework “AVFoundation”
use framework “AppKit”
use scripting additions
–http://piyocast.com/as/archives/4287

set aRes to retFileFormatUTI(“mov”) of me
–>  (NSString) “com.apple.quicktime-movie”

set aRes to retFileFormatUTI(“mp4″) of me
–>  (NSString) “public.mpeg-4″

set aRes to retFileFormatUTI(“m4v”) of me
–>  (NSString) “com.apple.m4v-video”

set aRes to retFileFormatUTI(“m4a”) of me
–>  (NSString) “com.apple.m4a-audio”

set aRes to retFileFormatUTI(“3gp”) of me
–>  (NSString) “public.3gpp”

set aRes to retFileFormatUTI(“3gp2″) of me
–>  (NSString) “public.3gpp2″

set aRes to retFileFormatUTI(“caf”) of me
–>  (NSString) “com.apple.coreaudio-format”

set aRes to retFileFormatUTI(“wav”) of me
–>  (NSString) “com.microsoft.waveform-audio”

set aRes to retFileFormatUTI(“aif”) of me
–>  (NSString) “public.aifc-audio”

set aRes to retFileFormatUTI(“aifc”) of me
–>  (NSString) “public.aifc-audio”

set aRes to retFileFormatUTI(“amr”) of me
–>  (NSString) “org.3gpp.adaptive-multi-rate-audio”

set aRes to retFileFormatUTI(“mp3″) of me
–>  (NSString) “public.mp3″

set aRes to retFileFormatUTI(“au”) of me
–>  (NSString) “public.au-audio”

set aRes to retFileFormatUTI(“ac3″) of me
–>  (NSString) “public.ac3-audio”

on retFileFormatUTI(aExt)
  set theWorkspace to current application’s NSWorkspace’s sharedWorkspace()
  
set valList to {“com.apple.quicktime-movie”, “public.mpeg-4″, “com.apple.m4v-video”, “com.apple.m4a-audio”, “public.3gpp”, “public.3gpp2″, “com.apple.coreaudio-format”, “com.microsoft.waveform-audio”, “public.aiff-audio”, “public.aifc-audio”, “org.3gpp.adaptive-multi-rate-audio”, “public.mp3″, “public.au-audio”, “public.ac3-audio”}
  
repeat with aUTI in valList
    if (theWorkspace’s filenameExtension:aExt isValidForType:aUTI) as boolean then
      return (current application’s NSString’s stringWithString:aUTI)
    end if
  end repeat
  
error “Invalid Constant String”
end retFileFormatUTI

★Click Here to Open This Script 

AppleScript名:ムービー系の拡張子からFile Format UTIを取得する v3
– Created 2016-10-24 by Takaaki Naganoya
– Modified 2016-10-25 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use framework “AVFoundation”
use scripting additions
use BridgePlus : script “BridgePlus” –Version 1.3.2以降
load framework

–http://piyocast.com/as/archives/4287

set aRes to retFileFormatUTI(“mov”) of me
–>  (NSString) “com.apple.quicktime-movie”

set aRes to retFileFormatUTI(“mp4″) of me
–>  (NSString) “public.mpeg-4″

set aRes to retFileFormatUTI(“m4v”) of me
–>  (NSString) “com.apple.m4v-video”

set aRes to retFileFormatUTI(“m4a”) of me
–>  (NSString) “com.apple.m4a-audio”

set aRes to retFileFormatUTI(“3gp”) of me
–>  (NSString) “public.3gpp”

set aRes to retFileFormatUTI(“3gp2″) of me
–>  (NSString) “public.3gpp2″

set aRes to retFileFormatUTI(“caf”) of me
–>  (NSString) “com.apple.coreaudio-format”

set aRes to retFileFormatUTI(“wav”) of me
–>  (NSString) “com.microsoft.waveform-audio”

set aRes to retFileFormatUTI(“aif”) of me
–>  (NSString) “public.aifc-audio”

set aRes to retFileFormatUTI(“aifc”) of me
–>  (NSString) “public.aifc-audio”

set aRes to retFileFormatUTI(“amr”) of me
–>  (NSString) “org.3gpp.adaptive-multi-rate-audio”

set aRes to retFileFormatUTI(“mp3″) of me
–>  (NSString) “public.mp3″

set aRes to retFileFormatUTI(“au”) of me
–>  (NSString) “public.au-audio”

set aRes to retFileFormatUTI(“ac3″) of me
–>  (NSString) “public.ac3-audio”

on retFileFormatUTI(aExt as string)
  return (current application’s SMSForder’s UTIForExtension:aExt)
end retFileFormatUTI

★Click Here to Open This Script 

2016/10/07 サウンド入出力を変更

Macのサウンド入出力先を変更するAppleScriptです。自分で作って使っているAppleScript Libraries「soundIO Lib」のインストールを必要とします(入っていない環境では構文確認自体を行えません)。

とくに、システム環境設定を起動したりすることはありません。すぐに実行は終了します。

本Scriptは実際のライブラリ呼び出しコードではなく、動作確認のためのコードなので、実際にはここまでしつこく設定内容の確認を行う必要はありません。ライブラリ側で切り替わったことを検証してtrue/falseを返しています。

書籍購入者で、書籍の感想を送って下さった方にsoundIO Libをプレゼントします。maro@piyocast.comまでご感想をお送りください

AppleScript名:サウンド入出力を変更
– Created 2016-10-07 by Takaaki Naganoya
– 2016 Piyomaru Software
– http://piyocast.com/as/archives/4252
use AppleScript version “2.4″
use scripting additions
use soundIO : script “soundIO Lib” version “1.0″

set targOutputDevice to “Logicool Z600″
set targIntputDevice to “Built-in Microphone”

–出力デバイス一覧に設定対象が入っているかチェック
set aList to soundIO’s getEveryAudioOutputDevice()
if targOutputDevice is not in aList then return false
–> {”Logicool Z600″, “Built-in Output”, “Mobiola Headphone”, “Mobiola Microphone”, “Soundflower (2ch)”, “Soundflower (64ch)”}

–入力デバイス一覧に設定対象が入っているかチェック
set bList to soundIO’s getEveryAudioInputDevice()
if targIntputDevice is not in bList then return false
–>  {”Built-in Microphone”, “Mobiola Headphone”, “Mobiola Microphone”, “Soundflower (2ch)”, “Soundflower (64ch)”}

–入出力デバイスを設定
set i1 to soundIO’s setAudioInuptDevice(targIntputDevice)
set o1 to soundIO’s setAudioOutuptDevice(targOutputDevice)

–サウンド入出力デバイスの変更確認
set i2 to soundIO’s getCurrentAudioInuptDevice()
set o2 to soundIO’s getCurrentAudioOutuptDevice()

set aRes to (i1 = true and o1 = true) and (i2 = targIntputDevice and o2 = targOutputDevice)
if aRes = true then
  tell current application
    display dialog “サウンド入出力を” & targOutputDevice & “に設定しました。”
  end tell
else
  tell current application
    display dialog “サウンド入出力の設定に失敗しました。”
  end tell
end if

★Click Here to Open This Script 

2016/06/18 指定フォルダ以下にあるMacDownとPages書類をソートしてPDFに書き出す

指定フォルダ以下にあるMacDownで記述したMarkdown書類と、Pages書類をすべての階層からピックアップしてファイル名でソートして、すべてデスクトップにPDFで書き出すAppleScriptです。

コンパイル(構文確認)および実行に際しては、Shane StanleyのScript Library「Bridge Plus」をインストールしておく必要があります。また、GUI Scriptingを利用しているため「システム環境設定」>「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でスクリプトエディタ(アプレットとして実行する場合にはアプレットそのもの)を登録して許可しておく必要があります。

「技術書典」に出す電子ブックのフォーマットがギリギリまで決まらず、しかも縦長のスクロールさせるタイプのものにできないかとあがいていたのですが、結局iPadあたりで読むことを考慮するとiPadのリーダーの仕様にしたがう必要があります。

……あれ?(^ー^; 結局、ページめくりは発生するし、一般的な本と同じような体裁になってしまいますよ → フォーマットがPDFになりました。

Markdown書類とPages書類が混在しているフォルダ構造のトップ階層のフォルダを指定すると、Markdown書類とPages書類をピックアップし、ファイル名でそれらをソートし、順次PDFに書き出すAppleScriptを書いてみました(必要は発明のマザー!)。

book1.png

ただ、MacDownには「書類をPDFに書き出す」という機能がAppleScript側に公開されていません(T_T)。

macdown_dict.png

ないものを「ないない」と嘆いても仕方がないので、さっさとGUI Scriptingで強制的にメニュー操作することにしました。

macdown_gui.png

で、どこに? どこに保存させるのでしょう??

大丈夫! そんなときには、保存ダイアログで幾つかのフォルダに強制的に移動させるキーボードショートカットが存在しており、Command-Dは「カレントディレクトリをデスクトップに移動させる」=「保存先をデスクトップにする」働きをします。

このため、保存先を操作しづらい(不可能とはいいませんけれども)GUI Scriptingにおいて保存先を指定することが、デスクトップフォルダについては可能になっています。

Pagesの方はひじょうに素直に(GUI Scriptingなんて使わずに)PDF書き出しが可能です。

そんなわけで、時間に追い詰められながらもなんとか大量のデータ処理を行っているのでありました。書き出した大量のPDFもAppleScriptでさくっと連結できるので、非常にいい感じです。あとは、本が完成すれば、、、、

AppleScript名:指定フォルダ以下にあるMDとPagesをソートしてPDFに書き出す
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–use spotLight : script “spotlightLib”

property searchRes : {}

load framework

set origPath to POSIX path of (choose folder with prompt “Markdown/Pages ファイルの入っているフォルダを選択”)

set aResList to (spotlightSearch(origPath, “kMDItemKind == ’Markdown’ || kMDItemKind == ’Pages 一般書類’”) of me) as list –Caution! this parameter is *localized*

–フルパスとファイル名のペアの2D Listを作成
set newList to {}
repeat with i in aResList
  set j to contents of i
  
set aStr to (current application’s NSString’s stringWithString:j)
  
set aFileName to aStr’s lastPathComponent()
  
set the end of newList to {aStr, aFileName}
end repeat

–番号順にソート
set sortIndexes to {1} –Key Item id: begin from 0, Sort by filename
set sortOrders to {true}
set sortTypes to {“compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(newList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
if resList = {} then return –No Result

–ソートした順番にMarkdownファイル/Pages書類ファイルをオープンしてデスクトップにPDF生成してクローズ
repeat with i in newList
  copy i to {fullPath, aFileName}
  
  
set apFile to (POSIX file (fullPath as string))
  
set anAlias to apFile as alias
  
set aFileName to aFileName as string
  
  
if aFileName ends with “.md” then
    exportFromMacDown(anAlias) of me –Markdown
  else if aFileName ends with “.pages” then
    exportFromPages(anAlias) of me –Pages
  end if
end repeat

–指定のPagesファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromPages(anAlias)
  tell application “Finder”
    set aName to name of anAlias
  end tell
  
  
set dtPath to (path to desktop) as string
  
set outPath to dtPath & aName & “.pdf”
  
  
tell application “Pages”
    close every document without saving
    
open anAlias
    
export document 1 to file outPath as PDF with properties {image quality:Best}
    
close every document without saving
  end tell
end exportFromPages

–指定のMacDownファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromMacDown(anAlias)
  tell application “MacDown”
    open {anAlias}
  end tell
  
  
tell current application
    delay 1 –ここの時間待ちが少ないと画像抜けが発生?
  end tell
  
macDownForceSave() of me
  
  
tell application “MacDown”
    close every document without saving
  end tell
end exportFromMacDown

–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceSave()
  activate application “MacDown”
  
tell application “System Events”
    tell process “MacDown”
      – File > Export > PDF
      
click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
      
      
–Go to Desktop Folder
      
keystroke “d” using {command down}
      
      
–Save Button on Sheet
      
click button 1 of sheet 1 of window 1
    end tell
  end tell
end macDownForceSave

–Spotlight Libの内容を引っ張り出してきた
on spotlightSearch(origPOSIXpath, aCondition)
  
  
set searchRes to {} –initialize
  
  
initiateSearchForFullPath(aCondition, origPOSIXpath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while searchRes = {}
    current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
  
set anObj to searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
–set anAttrList to anObj’s attributes() –”mdls” attributes
  
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  –  
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2016/05/11 ShangriLa Anime API V1のデータを取得する

技術書典の一般向けサイトで、参加サークルの詳細な説明文が見られるようになったので、ひととおり見てみました。すると、ノーマークだったサークルが面白いことをやっていることが分かったので、ちょっと見てみました。

ブースB-11の「秋葉原IT戦略研究所」さんが「SNSデータ解析で見る2016年アニメ界の展望」という本を出されるそうで、それだけだと何のことかわからなかったのですが、Twiter上でハッシュタグをつけてつぶやいている膨大なデータを分析してみた、という話だと理解しました。

その活動の一環として、「秋葉原IT戦略研究所」さんではRESTful APIで呼べるアニメ情報データベースを公開されており、とくに認証も何もかかっていないので気楽に呼べそうです。

そこで、実際にAppleScriptから呼んでみることにしました。実行にはShane StanleyのAppleScript Libraries「Bridge Plus」のインストールを必要とします。

まずは、このデータベースが対象にしているクール(1クール13話、1年を52週と仮定したときに1年は4クール)の情報を取得してみました。各クールが何年何月何日から何年何月何日までなのか、という情報は提供してくれないため、各自でカレンダー計算を行う必要がありそうです(4/1なのに前クールの最終回を放映していたというパターンもあるので、そのあたりどうなるのかルールが少々不明)。

AppleScript名:GET method REST API_Anime API_get cours
– Created 2016-05-11 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://api.moemoe.tokyo/anime/v1/master/cours”

set aRes to callRestGETAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
set aRESCode to responseCode of aRes
–>  200
set aRESHeader to responseHeader of aRes
–>  {Connection:”keep-alive”, Access-Control-Allow-Origin:”*”, Content-Type:”application/json; charset=utf-8″, Content-Length:”353″, Server:”nginx/1.8.0″, Date:”Wed, 11 May 2016 01:14:49 GMT”}

return aRESTres
–>  (NSDictionary) {7:{id:7, year:2015, cours:3}, 3:{id:3, year:2014, cours:3}, 8:{id:8, year:2015, cours:4}, 4:{id:4, year:2014, cours:4}, 9:{id:9, year:2016, cours:1}, 5:{id:5, year:2015, cours:1}, 1:{id:1, year:2014, cours:1}, 6:{id:6, year:2015, cours:2}, 10:{id:10, year:2016, cours:2}, 2:{id:2, year:2014, cours:2}}

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  load framework
  
–Request  
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
–Parse Results
  
set resList to aRes as list
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

★Click Here to Open This Script 

次に、指定した年のアニメ作品に関する情報を取得。

AppleScript名:GET method REST API_Anime API_getInfo_in_a_year
– Created 2016-05-11 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://api.moemoe.tokyo/anime/v1/master/” & “2016″

set aRes to callRestGETAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
set aRESCode to responseCode of aRes
–>  200
set aRESHeader to responseHeader of aRes
–>  {Connection:”keep-alive”, Access-Control-Allow-Origin:”*”, Content-Type:”application/json; charset=utf-8″, Content-Length:”353″, Server:”nginx/1.8.0″, Date:”Wed, 11 May 2016 01:14:49 GMT”}

return aRESTres
–>  (NSArray) {{id:281, title:”機動戦士ガンダム サンダーボルト”}, {id:282, title:”プリンス・オブ・ストライド オルタナティブ”}, {id:283, title:”無彩限のファントム・ワールド”}, {id:284, title:”ハルチカ〜ハルタとチカは青春する〜”}, {id:285, title:”ノルン+ノネット”}, {id:286, title:”アクティヴレイド −機動強襲室第八係−”}, {id:287, title:”少女たちは荒野を目指す”}, {id:288, title:”僕だけがいない街”}, {id:289, title:”おじさんとマシュマロ”}, {id:290, title:”ファンタシースターオンライン2 ジ アニメーション”}, {id:291, title:”だがしかし”}, {id:292, title:”暗殺教室(第2期)”}, {id:293, title:”ディバインゲート”}, {id:294, title:”おしえて!ギャル子ちゃん”}, {id:295, title:”石膏ボーイズ”}, {id:296, title:”霊剣山 星屑たちの宴”}, {id:297, title:”GATE 自衛隊 彼の地にて、斯く戦えり(2期)”}, {id:298, title:”昭和元禄落語心中”}, {id:299, title:”紅殻のパンドラ”}, {id:300, title:”ブブキ・ブランキ”}, {id:301, title:”ラクエンロジック”}, {id:302, title:”デュラララ!!×2 結”}, {id:303, title:”ナースウィッチ小麦ちゃんR”}, {id:304, title:”虹色デイズ”}, {id:305, title:”大家さんは思春期!”}, {id:306, title:”Dimension W”}, {id:307, title:”灰と幻想のグリムガル”}, {id:308, title:”シュヴァルツェスマーケン”}, {id:309, title:”最弱無敗の神装機竜(バハムート)”}, {id:310, title:”赤髪の白雪姫(第2期)”}, {id:311, title:”てーきゅう(第7期)”}, {id:312, title:”魔法少女なんてもういいですから。”}, {id:313, title:”蒼の彼方のフォーリズム”}, {id:314, title:”この素晴らしい世界に祝福を!”}, {id:315, title:”亜人”}, {id:316, title:”FAIRY TAIL ZERO”}, {id:317, title:”ももくり”}, {id:318, title:”この男子、魔法がお仕事です。”}, {id:319, title:”SUSHI POLICE”}, {id:320, title:”血液型くん!4″}, {id:321, title:”迷家‐マヨイガ‐”}, {id:322, title:”宇宙パトロールルル子”}, {id:323, title:”機動戦士ガンダムユニコーン RE:0096″}, {id:324, title:”影鰐-KAGEWANI-承”}, {id:325, title:”ぼのぼの”}, {id:326, title:”フューチャーカード バディファイト トリプルディー”}, {id:327, title:”逆転裁判”}, {id:328, title:”学戦都市アスタリスク 2nd SEASON”}, {id:329, title:”僕のヒーローアカデミア”}, {id:330, title:”マクロス”}, {id:331, title:”コンクリート・レボルティオ〜超人幻想〜THE LAST SONG”}, {id:332, title:”くまみこ”}, {id:333, title:”怪盗ジョーカー(シーズン3)”}, {id:334, title:”ばくおん!!”}, {id:335, title:”聖戦ケルベロス 竜刻のファタリテ”}, {id:336, title:”ハンドレッド”}, {id:337, title:”薄桜鬼〜御伽草子〜”}, {id:338, title:”ジョーカー・ゲーム”}, {id:339, title:”双星の陰陽師”}, {id:340, title:”SUPER LOVERS”}, {id:341, title:”鬼斬”}, {id:342, title:”文豪ストレイドッグス”}, {id:343, title:”あんハピ♪”}, {id:344, title:”クロムクロ”}, {id:345, title:”ネトゲの嫁は女の子じゃないと思った?”}, {id:346, title:”甲鉄城のカバネリ”}, {id:347, title:”少年メイド”}, {id:348, title:”坂本ですが?”}, {id:349, title:”田中くんはいつもけだるげ”}, {id:350, title:”キズナイーバー”}, {id:351, title:”はいふり”}, {id:352, title:”ふらいんぐうぃっち”}, {id:353, title:”とんかつDJアゲ太郎”}, {id:354, title:”三者三葉”}, {id:355, title:”うさかめ”}, {id:356, title:”マギ シンドバッドの冒険”}, {id:357, title:”Re:ゼロから始める異世界生活”}, {id:358, title:”うしおととら(第3クール)”}, {id:359, title:”ワガママハイスペック”}, {id:360, title:”ジョジョの奇妙な冒険 ダイヤモンドは砕けない”}, {id:361, title:”テラフォーマーズ リベンジ”}, {id:362, title:”プリパラ(3rdシーズン)”}, {id:363, title:”エンドライド”}, {id:364, title:”ビッグオーダー”}}

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  load framework
  
–Request  
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
–Parse Results
  
set resList to aRes as list
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

★Click Here to Open This Script 

最後に、年およびクール(1〜4)を指定して作品情報を取得するものです。

AppleScript名:GET method REST API_Anime API_getInfo_in_a_year_and_cour
– Created 2016-05-11 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set aYear to 2016
set aCour to 1

set reqURLStr to “http://api.moemoe.tokyo/anime/v1/master/” & (aYear as string) & “/” & (aCour as string)

set aRes to callRestGETAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
set aRESCode to responseCode of aRes
–>  200
set aRESHeader to responseHeader of aRes
–>  {Connection:”keep-alive”, Access-Control-Allow-Origin:”*”, Content-Type:”application/json; charset=utf-8″, Content-Length:”353″, Server:”nginx/1.8.0″, Date:”Wed, 11 May 2016 01:14:49 GMT”}

return aRESTres
–>  (NSArray) {{id:281, title_short3:”", sex:0, sequel:0, created_at:”2016-01-01 23:40:06.0″, public_url:”http://gundam-tb.net/”, twitter_hash_tag:”gundam_tb”, title:”機動戦士ガンダム サンダーボルト”, updated_at:”2016-01-01 23:40:06.0″, twitter_account:”gundam_tb”, title_short1:”サンダーボルト”, title_short2:”", cours_id:9}, …….

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  load framework
  
–Request  
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
–Parse Results
  
set resList to aRes as list
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

★Click Here to Open This Script 

放送されている番組の情報を提供する情報発信元としては、新聞などにテレビ番組のラテ欄情報を提供している東京ニュース通信社あるいは日刊編集センターか、EPGのメタデータを提供している(株)エムデータあたりがBtoB向け専門でやっています。BtoC向けにデータを出すかどうかは不明ですが、話をしてみるといいんじゃないでしょうか?

2016/05/02 Google APIを用いてURLを短縮してより小さいQRコードを作成

Google APIを利用して指定のURLを短縮し、QRコードを作成するAppleScriptです。

各種WebサービスをAppleScriptから呼び出して、日々便利に使っています。その中でまったく興味が湧かなかったURL短縮サービスAPI(URL Shortening API)。

たまたま、WebのURLからQRコードを作成して印刷することを検討していたときに、印刷面積を減らしたいというニーズが出てきました(TEPRAで印刷しようとしていたので)。

単純に縮小して印刷する物理的な大きさを小さくすれば、印刷できないこともないのですが、ゴミなどが付着したり経年劣化で退色した場合などに、むりやり小さく印刷するとエラーに遭遇する確率が上がってしまいます。また、解像度の低いプリンターで印刷するときには、エラー発生リスクが上がります。

そこで、長くなりがちなWebのURLを、短縮URLサービスを用いて短くしたうえでQRコード化することを思いつきました(Googleで探してみると、同様の先行事例多数 ^ー^;)。

qrcodes.png

上の図で、左がオリジナルのURLをQRコード化したもの。右がURL短縮してQRコード化したものです。URL短縮がQRコードのサイズの縮小に貢献することが見て取れます。ここで用いた元URLは、

 Original URL—-http://piyocast.com/as/archives/4067
 Shorten URL—-http://goo.gl/kT372B

クラウドストレージ上にあるファイル(Dropboxなど)やGoogle Map上の位置情報を示すURLは長くなりがちですが、いったんURL短縮サービスを経由することで、QRコードに印刷するのに抵抗感のない程度の長さの文字列に圧縮でき、たいへんけっこうなことです(Google Mapsにも短縮URL変換の機能がついていますね、、、)。

実行時には、Googleアカウントを作成してGoogle APIの利用申請を行い、API Keyを取得してAppleScript中に指定してください。また、実行にあたって実行環境にShane StanleyのAppleScript Libraries「Bridge Plus」がインストールしてあることが条件となります。

AppleScript名:POST method REST API_Google Shortener URL and make QR code
– Created 2016-05-01 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

property APIKey : “XXxxXxXXXxXxX-XxXXxXXXxxxxXXXXxXxXXxXXX” –Google API Key

set aLongURL to “http://piyocast.com/as/archives/4067″ –Target Long URL
set aShortURL to shortenURL(aLongURL) of me
if aShortURL = “” then return –Error

set dtPath to POSIX path of (path to desktop) & ((current application’s NSUUID’s UUID()’s UUIDString())’s stringByAppendingString:“.png”)
set aRes to writeQRimageAsPNG(aShortURL, dtPath) of me

–指定パスにQRコードをPNG画像で出力
on writeQRimageAsPNG(aTargStr, aTargPath)
  set aStr to current application’s NSString’s stringWithString:aTargStr
  
set strData to aStr’s dataUsingEncoding:(current application’s NSISOLatin1StringEncoding)
  
set qrFilter to current application’s CIFilter’s filterWithName:“CIQRCodeGenerator”
  
qrFilter’s setValue:strData forKey:“inputMessage”
  
qrFilter’s setValue:“H” forKey:“inputCorrectionLevel”
  
set anImage to qrFilter’s outputImage()
  
saveNSImageAtPathAsPNG(anImage, aTargPath) of me
end writeQRimageAsPNG

–NSImageを指定パスにPNG形式で保存
on saveNSImageAtPathAsPNG(anImage, outPath)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to current application’s NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to current application’s NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(current application’s NSPNGFileType) |properties|:(missing value))
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –成功ならtrue、失敗ならfalseが返る
end saveNSImageAtPathAsPNG

–与えられたURL文字列を短縮する
on shortenURL(aLongURL)
  set myAPIKey to my APIKey
  
set reqURLStr to “https://www.googleapis.com/urlshortener/v1/url?key=” & myAPIKey
  
set aRec to {longUrl:aLongURL}
  
set aRes to callRestPOSTAPIAndParseResults(reqURLStr, aRec) of me
  
  
set aRESTres to json of aRes
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
  
if aRESCode is not equal to 200 then return “”
  
set aShort to (aRESTres’s valueForKey:“id”) as string
  
return aShort
end shortenURL

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aPostData)
  
  
load framework
  
  
–Request  
  
set dataJson to current application’s NSJSONSerialization’s dataWithJSONObject:aPostData options:0 |error|:(missing value)
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setHTTPBody:dataJson
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
  
–CALL REST API
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
  
–Parse Results
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

★Click Here to Open This Script 

2016/03/13 Google Translate APIでサポート言語一覧を指定言語で取得する

Google Translate API(REST API)を呼び出して、翻訳可能な言語の名称一覧を指定言語(Japanese)で取得するAppleScriptです。

実行するには、Shane StanleyのBridgePlus Script Libraryを実行するMacの~/Library/Script Librariesにインストールし、GoogleのAPI利用登録を行ってAPI Keyを取得しておく必要があります。ご自分のAPI Keyを下記リストのmyAPIKeyに代入(リスト中では伏字)してから実行してください。

実行結果から、Google Translate APIが104の言語をサポートしていることがわかります。

この程度の問い合わせなら問題なく実行できているのですが、いざ翻訳させようとするとまだクリアーできていない困難が(汗) Googleのサービス側のログとかを見られると、どこで間違っているのか分かりそうですが、、、、というか、そういうサービス自体が存在していそうな、、、、

AppleScript名:GET method REST API_Google Translate API サポート言語一覧を取得
– Created 2016-03-03 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus” –https://www.macosxautomation.com/applescript/apps/BridgePlus.html

set myAPIKey to “XXxxXxXXXxXxX-XxXXxXXXxxxxXXXXxXxXXxXXX”
set reqURLStr to “https://www.googleapis.com/language/translate/v2/languages?key=” & myAPIKey & “&target=ja”

set aRes to callRestGETAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
–>  (NSDictionary) {error:{message:”Bad Request”, errors:{{reason:”keyInvalid”, message:”Bad Request”, domain:”usageLimits”}}, code:400}}–エラー時

–>  (NSDictionary) {data:{languages:{{name:”アイスランド語”, language:”is”}…… {name:”英語”, language:”en”}, {name:”韓国語”, language:”ko”}, {name:”中国語(簡体)”, language:”zh”}, {name:”中国語(繁体)”, language:”zh-TW”}, {name:”日本語”, language:”ja”}}}}

–return (aRESTres’s valueForKeyPath:”data.languages.name”)’s |count|()
–>  104

set aRESCode to responseCode of aRes
–>  200

set aRESHeader to responseHeader of aRes
–>  {Content-Type:”application/json; charset=UTF-8″, alt-svc:”quic=\”:443\”; ma=2592000; v=\”31,30,29,28,27,26,25\”", alternate-protocol:”443:quic,p=1″, Content-Encoding:”gzip”, Server:”GSE”, x-xss-protection:”1; mode=block”, Expires:”Sun, 13 Mar 2016 00:45:39 GMT”, Cache-Control:”private, max-age=0″, Date:”Sun, 13 Mar 2016 00:45:39 GMT”, Content-Length:”132″, x-content-type-options:”nosniff”, x-frame-options:”SAMEORIGIN”, Vary:”Origin, X-Origin”}

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  load framework
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestGETAPIAndParseResults

★Click Here to Open This Script 

2016/03/06 REST APIに対してGET、POST、PUT、DELETEのmethodを呼び出す

AppleScriptでWeb上のREST APIを呼び出すじっけんです。Web上にあるREST APIの実験用サービスJSONPlaceholderをAppleScriptから呼び出して各種メソッド呼び出しの実証を行ってみました。

できてしまえばたいしたことはありませんが、地道に調査しておかないと今日明日でいきなりやれと言われても困ってしまいます。

実際に、NTTdocomoの形態素解析のREST APIをmethod=POSTでAppleScriptから呼び出してみたところ、

–> (NSDictionary) {request_id:”record001″, word_list:{{{”私”}, {”の”}, {”名前”}, {”は”}, {” ”}, {”長野”}, {”谷”}, {”です”}, {”。”}}}, info_filter:”form”}

となりました(words ofの実行結果と変わり映えしない、、、、)。固有名詞としてあらかじめ人名を登録しておきたいケースがほとんどなので、ちょっとこれだと使えない感じではあります。ただ、実際に稼働しているAPIを呼べることは実証できました。

AppleScript名:GET method REST API
– Created 2016-03-03 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://jsonplaceholder.typicode.com/posts”

set aRes to callRestGETAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
–>  (NSArray) {{body:”quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto”, id:1, title:”sunt aut facere repellat provident occaecati excepturi optio reprehenderit”, userId:1},…

set aRESCode to responseCode of aRes
–>  200

set aRESHeader to responseHeader of aRes
–>  {Content-Type:”application/json; charset=utf-8″, Pragma:”no-cache”, X-Powered-By:”Express”, Via:”1.1 vegur”, Server:”Cowboy”, Expires:”-1″, Cache-Control:”no-cache”, Date:”Sun, 06 Mar 2016 07:27:26 GMT”, Access-Control-Allow-Credentials:”true”, Content-Length:”27520″, Connection:”keep-alive”, X-Content-Type-Options:”nosniff”, Etag:”W/\”6b80-uPwhAkDat3Fl5TugzmyYpQ\”", Vary:”Origin”}

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  load framework
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestGETAPIAndParseResults

★Click Here to Open This Script 

AppleScript名:POST method REST API
– Created 2016-03-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://jsonplaceholder.typicode.com/posts”
set aPostData to {title:“foo”, body:“bar”, userId:1}

set aRes to callRestPOSTAPIAndParseResults(reqURLStr, aPostData) of me

set aRESTres to json of aRes
–>  (NSDictionary) {body:”bar”, id:101, title:”foo”, userId:1}

set aRESCode to responseCode of aRes
–>  200

set aRESHeader to responseHeader of aRes
–>  {Content-Type:”application/json; charset=utf-8″, Pragma:”no-cache”, X-Powered-By:”Express”, Via:”1.1 vegur”, Server:”Cowboy”, Expires:”-1″, Cache-Control:”no-cache”, Date:”Sun, 06 Mar 2016 07:47:56 GMT”, Access-Control-Allow-Credentials:”true”, Content-Length:”65″, Connection:”keep-alive”, X-Content-Type-Options:”nosniff”, Etag:”W/\”41-DL1IzEbjanDFwm6hF2BfJw\”", Vary:”Origin, X-HTTP-Method-Override”}

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aPostData)
  load framework
  
set dataJson to current application’s NSJSONSerialization’s dataWithJSONObject:aPostData options:0 |error|:(missing value)
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setHTTPBody:dataJson
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

★Click Here to Open This Script 

AppleScript名:PUT method REST API
– Created 2016-03-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://jsonplaceholder.typicode.com/posts/1″
set aPostData to {title:“ぴよまるソフトウェア”, body:“PUTのじっけん”, userId:1}
–set aPostData to {title:”foo”, body:”bar”, userId:1}

set aRes to callRestPUTAPIAndParseResults(reqURLStr, aPostData) of me

set aRESTres to json of aRes
–>  (NSDictionary) {body:”PUTのじっけん”, id:1, title:”ぴよまるソフトウェア”, userId:1}
return aRESTres
set aRESCode to responseCode of aRes
–>  200

set aRESHeader to responseHeader of aRes
–>  {Content-Type:”application/json; charset=utf-8″, Pragma:”no-cache”, X-Powered-By:”Express”, Via:”1.1 vegur”, Server:”Cowboy”, Expires:”-1″, Cache-Control:”no-cache”, Date:”Sun, 06 Mar 2016 07:47:56 GMT”, Access-Control-Allow-Credentials:”true”, Content-Length:”65″, Connection:”keep-alive”, X-Content-Type-Options:”nosniff”, Etag:”W/\”41-DL1IzEbjanDFwm6hF2BfJw\”", Vary:”Origin, X-HTTP-Method-Override”}

–PUT methodのREST APIを呼ぶ
on callRestPUTAPIAndParseResults(aURL, aPostData)
  load framework
  
set dataJson to current application’s NSJSONSerialization’s dataWithJSONObject:aPostData options:0 |error|:(missing value)
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
  
aRequest’s setHTTPMethod:“PUT”
  
aRequest’s setHTTPBody:dataJson
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Accept”
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPUTAPIAndParseResults

★Click Here to Open This Script 

AppleScript名:DELETE method REST API
– Created 2016-03-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://jsonplaceholder.typicode.com/posts/1″

set aRes to callRestDELETEAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes
–>  (NSDictionary) {}

set aRESCode to responseCode of aRes
–>  200

set aRESHeader to responseHeader of aRes
–>  {Content-Type:”application/json; charset=utf-8″, Pragma:”no-cache”, X-Powered-By:”Express”, Via:”1.1 vegur”, Server:”Cowboy”, Expires:”-1″, Cache-Control:”no-cache”, Date:”Sun, 06 Mar 2016 07:53:51 GMT”, Access-Control-Allow-Credentials:”true”, Content-Length:”2″, Connection:”keep-alive”, X-Content-Type-Options:”nosniff”, Etag:”W/\”2-mZFLkyvTelC5g8XnyQrpOw\”", Vary:”Origin”}

–DELETE methodのREST APIを呼ぶ
on callRestDELETEAPIAndParseResults(aURL)
  load framework
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
set respF to false
  
  
aRequest’s setHTTPMethod:“DELETE”
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestDELETEAPIAndParseResults

★Click Here to Open This Script 

2016/03/06 REST APIを呼ぶじっけん v2

Web上のREST APIのサービスを呼び出すAppleScriptの改良版です。

v1ではただREST APIを呼んで、結果をNSStringで取得するだけでしたが、各種仕様を確認してJSON文字列とみなしNSJSONSerializationでparseしてNSDictionaryなりNSDictionary in NSArrayにして返します。

REST APIの返り値についてはXML/JSONのどちらを用いるかとくに規約はないようですが、一般的にはJSONとのことなのでJSONとみなしてparse。また、parseした結果をAppleScriptのオブジェクトにいったん変換してしまうと、構造上データを取り出せなくなってしまうケースがあるため、Cocoaオブジェクトの状態のままで返すようにしてみました(json of aRes)。

本ScriptはMethod=GETで呼んでいる超簡単なAPIの呼び出し例ですが、その他のケース(POST、PUT、DELETE、HEAD、OPTIONS、PATCH、COPY、SEARCH)についても呼び出し方を調べておきたいところです。

AppleScript名:REST APIを呼ぶじっけん v2
– Created 2016-03-03 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

set reqURLStr to “http://demos.xojo.com/EEWS/index.cgi/GetAllCustomers”
–set reqURLStr to “https://api.github.com/repos/mmattozzi/cocoa-rest-client/releases”

set aRes to callRestAPIAndParseResults(reqURLStr) of me

set aRESTres to json of aRes –ASのRecordに変換してしまうと、あとで値が取り出せなくなるケースがあったので、NSDictionaryのまま返している
–>  (NSDictionary) {GetAllCustomers:{10044:{Zip:”63101″, City:”St. Louis”, FirstName:”Beatrice”, LastName:”Fulton”, State:”MO”}, …

set aRESCode to responseCode of aRes
–>  404

set aRESHeader to responseHeader of aRes
–>  {Content-Encoding:”gzip”, Content-Type:”text/html; charset=UTF-8″, Content-Length:”5376″, Connection:”close”, Server:”Apache/2.2.15 (CentOS)”, Date:”Sat, 05 Mar 2016 15:37:26 GMT”}

on callRestAPIAndParseResults(aURL)
  
  
load framework
  
  
set aRequest to current application’s NSURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
set respF to false
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
–log {length of resList}
  
–> {2}
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
–>  (NSString) “{”GetAllCustomers”:{”10174″:{”FirstName”:”Abdul”,”LastName”:”Mcconnell”,”City”:”Colorado Springs”,”State”:”CO”,”Zip”:”80935″},….
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
–set cRes to ASify from aJsonDict –json
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to ASify from (dRes’s statusCode())
  
–>  404
  
  
–Get Response Header
  
set resHeaders to ASify from (dRes’s allHeaderFields())
  
–>  (NSDictionary) {Content-Encoding:”gzip”, Content-Type:”text/html; charset=UTF-8″, Content-Length:”5376″, Connection:”close”, Server:”Apache/2.2.15 (CentOS)”, Date:”Thu, 03 Mar 2016 13:44:22 GMT”}
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestAPIAndParseResults

★Click Here to Open This Script 

2016/02/13 Myriad Tables Libによる表インタフェースの表示

Shane StanleyによるAppleScript Libraries「Myriad Tables Lib」のバージョン1.0.0を試してみました。

同ライブラリをダウンロードして、「Myriad Tables Lib.scptd」を~/Libraries/Script Libraries」フォルダに入れると利用可能になります。

以前からいろいろTable ViewをAppleScript上で表示させたりしていましたが、それのデラックス版です。同ライブラリにはAppleScript用語辞書やドキュメントもついているため、手軽に表インタフェースを表示できます。

table1.png

table3.png
▲ヘッダー行をクリックすると当該セルをキーとしたソートを行う

table4.png
▲ヘッダー行を再度クリックすると当該セルをキーとした逆順ソートを行う

table5.png
▲popup menuの表示も可能

table6.png
▲選択+入力が可能なcombo boxの表示も可能

AppleScriptによるワークフローにこうした表インタフェースをつけることの意味は、処理前のデータの確認(CSVファイルなど)、処理範囲の指定(選択範囲のみ処理するとか)、処理後の結果確認などを手軽に行えるというあたりにあります。

# テーブル内容のしぼりこみ表示が行えるとなおよいでしょう

データ処理を行う対象を指定するためだけにExcelが必要、といった話がよくありましたが、こうしたGUI部品をAppleScriptから手軽に(Xcodeを使わずに)必要な部分だけ呼び出せることにはメリットがあります。

table2.png
▲フルオプション指定時

いろいろとオプションを指定すると、行番号や選択範囲の削除/新規作成、カスタムビューの追加表示(accessory view)なども行えます。accessory viewについては、とりあえずNSImageViewを表示してみましたが、これに「飾り」以上の意味づけがあるのかどうかが気になります。

同梱のSampleを見てみたら、オプションのチェックボックスの値を取得していました。そういう使い方ができるもよう。ただし、accessory viewに指定できるビューの最大サイズに制限があるため、ここに巨大なWebViewを表示するとかMKMapViewを表示するのは苦しい。

この表インタフェース上ですべてのデータ入力を行ってもらうといった使い方は向いていないので(不可能ではないものの、途中でScriptがクラッシュしたときに途中のworkファイルが保存されていない)、あくまで内容確認、処理範囲選択などの用途に活用するとよいのではないでしょうか。

AppleScript名:かんたんなテーブルの表示
– Created 2016-02-07 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use script “Myriad Tables Lib”

set x to {a:1.4} – 未サポートクラス(record)は表示できないが、無視される
set theHeads to {“名”, “姓”, “インデックス”, “チェックボックス”, “スコア”, “日付”} –ヘッダー
set theDate to current date

set someData to {{“長野谷”, “隆昌”, 1, true, 10000, theDate, x}, ¬
  {“長野谷”, “ぴよまる”, 2, true, -13.5, theDate, x}, ¬
  {
“長野谷”, “ぴよこ”, 3, false, 9.0, theDate + 40000, x}, ¬
  {
“長野谷”, “ぴよぴよ”, 4, false, 1.23456789E+7, theDate + 50000, x}, ¬
  {
“長野谷”, “ぴよきち”, 5, true, 133.4567, theDate + 30000, x}, ¬
  {
“長野谷”, “ぴよまろ”, 6, false, 22.0, theDate + 60000, x}}

–テーブルの各列のデータ表示形式はこのテンプレートをもとにしている
set theTemplate to {“”, “”, 1, true, 1, current date, missing value}

–Step1- 必須操作。最初にデータを作成する必要がある
set myTable to make new table with data someData ¬
  with title “テーブルサンプル” column headings theHeads ¬
  
with prompt “複数行選択可。 すべてのデータは編集可能です。” editable columns {} ¬
  
row template theTemplate ¬
  
with multiple selections allowed and empty selection allowed

–Step 2-テーブルの表示オプション(ハイライト表示、枠線表示など)。本Stepは省略可
modify table myTable highlighted rows {} ¬
  grid style grid both dashed between rows ¬
  
OK button name “OK” with OK button is default

–Step 3テーブル表示を行う
set theResult to (display table myTable)
–>  {rows selected:{1}, values selected:{{”長野谷”, “隆昌”, 1, true, 10000, date “2016年2月8日月曜日 18:13:07″, {a:1.4}}}, values returned:{{”長野谷”, “隆昌”, 1, true, 10000, date “2016年2月8日月曜日 18:13:07″, {a:1.4}}, {”長野谷”, “ぴよまる”, 2, true, -13.5, date “2016年2月8日月曜日 18:13:07″, {a:1.4}}, {”長野谷”, “ぴよこ”, 3, false, 9.0, date “2016年2月9日火曜日 5:19:47″, {a:1.4}}, {”長野谷”, “ぴよぴよ”, 4, false, 1.23456789E+7, date “2016年2月9日火曜日 8:06:27″, {a:1.4}}, {”長野谷”, “ぴよきち”, 5, true, 133.4567, date “2016年2月9日火曜日 2:33:07″, {a:1.4}}, {”長野谷”, “ぴよまろ”, 6, false, 22.0, date “2016年2月9日火曜日 10:53:07″, {a:1.4}}}, button number:1, gave up:false}

★Click Here to Open This Script 

2016/02/09 指定URLのJSONをダウンロードしてRecordに変換 v2.1

指定URLのJSONデータを取得して、結果をrecordに変換するAppleScriptです。AppleScript Libraries「BridgePlus」を利用するように変更してみました。

AppleScript名:指定URLのJSONをダウンロードしてRecordに変換 v2.1
– Created 2016-02-06 by Takaaki Naganoya
– Modified 2016-02-08 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

–東京電力電力供給状況API JSON URL(指定された日時の電力使用状況を返す)
set a to downloadAndParseJSON(“http://tepco-usage-api.appspot.com/2016/2/6/19.json”) of me
–>  {forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:”2012-02-05 08:30:00″, usage:3720, forecast_peak_updated:”2012-02-05 08:30:00″, day:6, usage_updated:”2016-02-06 11:05:05″, capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:19, entryfor:”2016-02-06 10:00:00″}

–東京電力電力供給状況API JSON URL(指定された日の毎時の電力使用状況を、配列として返す)
set b to downloadAndParseJSON(“http://tepco-usage-api.appspot.com/2016/2/6.json”) of me
–>  {{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:”2012-02-05 08:30:00″, usage:3081, forecast_peak_updated:”2012-02-05 08:30:00″, day:6, usage_updated:”2016-02-05 16:05:06″, capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:0, entryfor:”2016-02-05 15:00:00″}…….{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:”2012-02-05 08:30:00″, usage:3645, forecast_peak_updated:”2012-02-05 08:30:00″, day:6, usage_updated:”2016-02-06 12:05:07″, capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:20, entryfor:”2016-02-06 11:00:00″}}

–東京電力電力供給状況API JSON URL(指定された月の毎日毎時の電力使用状況を、配列として返す)
set c to downloadAndParseJSON(“http://tepco-usage-api.appspot.com/2016/2.json”) of me
–>  {{forecast_peak_period:18, forecast_peak_usage:4260, capacity_updated:”2012-01-31 23:30:00″, usage:2901, forecast_peak_updated:”2012-01-31 23:30:00″, day:1, usage_updated:”2016-01-31 16:05:04″, capacity:4641, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:0, entryfor:”2016-01-31 15:00:00″}……{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:”2012-02-05 08:30:00″, usage:3645, forecast_peak_updated:”2012-02-05 08:30:00″, day:6, usage_updated:”2016-02-06 12:05:07″, capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:20, entryfor:”2016-02-06 11:00:00″}}

on downloadAndParseJSON(aURL)
  set aRes to downloadDataFromWeb(aURL, 30) of me
  
if aRes = false then return false –download error
  
  
set jsonString to current application’s NSString’s stringWithString:aRes
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
set bRes to ASify from aJsonDict
  
return bRes
end downloadAndParseJSON

on downloadDataFromWeb(aURL, timeOutSec)
  set aURL to current application’s |NSURL|’s alloc()’s initWithString:aURL
  
set aReq to current application’s NSURLRequest’s alloc()’s initWithURL:aURL cachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeOutSec
  
set urlRes to (current application’s NSURLConnection’s sendSynchronousRequest:aReq returningResponse:(missing value) |error|:(missing value))
  
  
if urlRes = missing value then
    return false –Download Error
  else
    set aVal to (current application’s NSString’s alloc()’s initWithData:urlRes encoding:(current application’s NSUTF8StringEncoding))
    
return aVal
  end if
end downloadDataFromWeb

★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/10/28 Keynote 6.6のじっけん(2)〜放射状にラインを引く

Keynote 6.6でドキュメント上に放射状にラインを引くじっけんです。手作業ではさすがにかったるい内容ですが、AppleScriptからであれば「やってみようかな」という気になる内容です。

実行にはShane StanleyのAppleScriptライブラリ「BridgePlus」をあらかじめインストールしておく必要があります。

kn1.png

(少々ごまかしつつも)さっくりできました。拡大するとこんなかんじです。

実用性はひたすらありませんが、こういう操作ができるようになったのかと思うと感慨深いものがあります。

kn2.png

AppleScript名:Keynote 6.6で放射状にラインを引く
– Created 2015-10-27 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use bPlus : script “BridgePlus”

set aNum to 250

load framework

tell application “Keynote”
  –新規ドキュメント作成
  
set aDoc to (make new document with properties {document theme:theme “ホワイト”, width:1024, height:768})
  
–set aDoc to (make new document with properties {document theme:theme “White”, width:1024, height:768})
  
tell aDoc
    set aHeight to height
    
set aWidth to width
    
    
set aCenterW to aWidth / 2
    
set aCenterH to aHeight / 2
    
    
set aMas to master slide “空白”
    
–set aMas to master slide “Blank”
    
    
tell slide 1
      set base slide to aMas
      
      
repeat with i from 1 to 130 by 6
        set {x, y} to calcSinCos(i, aCenterW, aCenterH, aNum) of me
        
make new line with properties {start point:{aCenterW, aCenterH}, end point:{x, y}, rotation:i}
      end repeat
      
    end tell
  end tell
end tell

on calcSinCos(i, aCenterW, aCenterH, aNum)
  tell current application
    set aSinNum to (current application’s SMSForder’s sinValueOf:i)
    
set aCosNum to (current application’s SMSForder’s cosValueOf:i)
    
set x to ((aNum * (aSinNum as real))) + aCenterW
    
set y to (aNum * (aCosNum as real)) + aCenterH
    
return {x, y}
  end tell
end calcSinCos

★Click Here to Open This Script 

2015/09/29 BridgePlus 1.2のframeworkの新機能テスト(1)

Shane StanleyのASObjCExtras.frameworkがAppleScript Library「BridgePlus」に進化するにあたって、同ライブラリのバンドル内にフレームワークが内包される形式に移行しました。

「フレームワークを直接(AppleScriptObjCになじみのない)scripterに使ってもらうのは敷居が高すぎる」とShaneが判断し、ラッパーを介することで使いやすさを増したのが「BridgePlus」である、といえます。BridgePlusではCocoaの値を返すことを極力避け、listやtextなどのScripterになじみ深いデータ型にかならず変換して値を返すように細心の注意が払われています。型変換のためにオーバーヘッドが発生しても、とっつきやすさを優先させたわけです。

ただ、逆をいえばBrdgePlusの本体は内包されているフレームワークであって、Script Libraryはその入れ物(+呼び出し用のハンドラ群+AppleScript用語辞書)にすぎません。

BridgePlusに移行してから、内部のframeworkの機能についてはノーマークだったのですが、先日のIM関連情報取得や状態設定にみられるような「お得な機能」をチェックしておくべきと考え、ひととおり調べてみました(朝のランニングにしてはハードなものになりました)。

結論からいえば、ひじょうに有用性の高い機能がいくつも見つかり・・・個人的には有用な調査が行えました。量が多いので、まだすべてを確認し終わっていませんが、9割ぐらいはカバーできていると思います。

なお、返り値についてはASObjC Explorer 4のCocoaログ機能で表示されたものをそのまま記載しており、NSConcreteValueというのはログのためにASObjC Explorer 4内部で定義しているものであり、実際の内容を示すものではありません(無視してOK)。

アップデート:
Shaneから「resourceValueForKey: forURLsOrFiles:」について正しい呼び出し方について指摘があり、「NSURLを利用するように設計してあり、指定のパス(ディレクトリ)内の各ファイルについてループせずに特定の属性を取得するようになっている」とのこと。

AppleScript名:BrdgePlus 1.2のframeworkの新機能てすと1.1
– Created 2015-09-29 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus” version “1.2″

load framework

– New in v1.2.0

– House-keeping methods

set aRes to BridgePlus’s SMSForder’s frameworkDate()
–>  (NSString) “Sep 23 2015, 15:37:08″

– Regular Expression methods

– Returns the first matching string found. If none found, returns missing value.
set bRes to BridgePlus’s SMSForder’s findFirstMatch:“[98]\\d{6}” inString:“#98158084 Xxxxx Xxxxx xxxx” options:“ix”
–>  (NSString) “9815808″

–Returns the first match record found. If none found, returns missing value.
set cRes to BridgePlus’s SMSForder’s findFirstMatchRecord:“[98]\\d{6}” inString:“#98158084 Xxxxx Xxxxx xxxx” options:“ix”
–>  (NSDictionary) {​​​​​foundRange:(NSConcreteValue) NSRange: {1, 7}, ​​​​​captureGroup:0, ​​​​​foundString:”9815808″​​​}

–Returns an array of the matching strings found. If none found, returns an empty array.
set dRes to BridgePlus’s SMSForder’s findMatches:“[98]\\d{6}” inString:“#98158084 Xxxxx 98000000 xxxx” options:“ix”
–>  (NSArray) {​​​​​”9815808″, ​​​​​”9800000″​​​}

–Returns an array of the match records found. If none found, returns an empty array.
set eRes to BridgePlus’s SMSForder’s findMatchRecords:“[98]\\d{6}” inString:“#98158084 Xxxxx 98000000 xxxx” options:“ix”
–>  (NSArray) {​​​​​{​​​​​​​foundRange:(NSConcreteValue) NSRange: {1, 7}, ​​​​​​​captureGroup:0, ​​​​​​​foundString:”9815808″​​​​​}, ​​​​​{​​​​​​​foundRange:(NSConcreteValue) NSRange: {16, 7}, ​​​​​​​captureGroup:0, ​​​​​​​foundString:”9800000″​​​​​}​​​}

– Returns an array for each match, sorted in your specified capture group order. If you specify non-existent capture groups or capture groups that do not participate in a particular match, they will be represented by missing value. If no matches are found, the overall result will be missing value.
set fRes to BridgePlus’s SMSForder’s findMatches:“[98]\\d{6}” inString:“#98158084 Xxxxx 98000000 xxxx” options:“ix” captureGroups:{0}
–>  (NSArray) {​​​​​{​​​​​​​”9815808″​​​​​}, ​​​​​{​​​​​​​”9800000″​​​​​}​​​}

– Returns an array of match records for each match, sorted in your specified capture group order. If no matches are found, the overall result will be missing value.
set gRes to BridgePlus’s SMSForder’s findMatchRecords:“[98]\\d{6}” inString:“#98158084 Xxxxx 98000000 xxxx” options:“ix” captureGroups:{0}
–>  (NSArray) {​​​​​{​​​​​​​{​​​​​​​​​foundRange:(NSConcreteValue) NSRange: {1, 7}, ​​​​​​​​​captureGroup:0, ​​​​​​​​​foundString:”9815808″​​​​​​​}​​​​​}, ​​​​​{​​​​​​​{​​​​​​​​​foundRange:(NSConcreteValue) NSRange: {16, 7}, ​​​​​​​​​captureGroup:0, ​​​​​​​​​foundString:”9800000″​​​​​​​}​​​​​}​​​}

– Substring extraction methods

set hRes to BridgePlus’s SMSForder’s charactersOfString:“abcdefghijklmnopqrstuvwxyz”
–>  (NSArray) {​​​​​”a”, ​​​​​”b”, ​​​​​”c”, ​​​​​”d”, ​​​​​”e”, ​​​​​”f”, ​​​​​”g”, ​​​​​”h”, ​​​​​”i”, ​​​​​”j”, ​​​​​”k”, ​​​​​”l”, ​​​​​”m”, ​​​​​”n”, ​​​​​”o”, ​​​​​”p”, ​​​​​”q”, ​​​​​”r”, ​​​​​”s”, ​​​​​”t”, ​​​​​”u”, ​​​​​”v”, ​​​​​”w”, ​​​​​”x”, ​​​​​”y”, ​​​​​”z”​​​}

set iRes to BridgePlus’s SMSForder’s wordsOfString:“This is a pen.” – ignore localization
–>  (NSArray) {​​​​​”This”, ​​​​​”is”, ​​​​​”a”, ​​​​​”pen”​​​}

set jRes to BridgePlus’s SMSForder’s localizedWordsOfString:“私の名前は長野谷です。” –consider localization, but not consider proper noun or person’s name
–>  (NSArray) {​​​​​”私”, ​​​​​”の”, ​​​​​”名前”, ​​​​​”は”, ​​​​​”長野”, ​​​​​”谷”, ​​​​​”です”​​​} –{”長野”,”谷”} have to be {”長野谷”} . But it is person’s name.

set kRes to BridgePlus’s SMSForder’s sentencesOfString:“Hello. Goodby. See-you again!” – ignore localization
–>  (NSArray) {​​​​​”Hello. “, ​​​​​”Goodby. “, ​​​​​”See-you again!”​​​}

set lRes to BridgePlus’s SMSForder’s localizedSentencesOfString:“こんにちは。こんばんは。さようなら。えっ?!それはたいへんだ!! 「東京には空がない」と智恵子は言った。”
–>  (NSArray) {​​​​​”こんにちは。”, ​​​​​”こんばんは。”, ​​​​​”さようなら。”, ​​​​​”えっ?!”, ​​​​​”それはたいへんだ!!「”, ​​​​​”東京には空がない」と智恵子は言った。”​​​} — 5th item seems wrong

set mRes to BridgePlus’s SMSForder’s paragraphsOfString:“Hello. Goodby. See-you again!
Hello2. Goodby2. See-you again2!”

–>  (NSArray) {​​​​​”Hello. Goodby. See-you again!”, ​​​​​”Hello2. Goodby2. See-you again2!”​​​}

set nRes to BridgePlus’s SMSForder’s stringsOfString:“xxxx” inString:“#98158084 Xxxxx 98000000 xxxx” options:(current application’s NSCaseInsensitiveSearch)
–>  (NSArray) {​​​​​”Xxxx”, ​​​​​”xxxx”​​​}

set oRes to BridgePlus’s SMSForder’s stringsOfString:“ひよこ” inString:“ひょこっと登場したひよこだよびよこぴよこ” options:(current application’s NSCaseInsensitiveSearch) locale:(current application’s NSLocale’s localeWithLocaleIdentifier:“ja”)
–>  (NSArray) {​​​​​”ひよこ”​​​}

– Substring range methods

set pRes to BridgePlus’s SMSForder’s rangesOfCharactersOfString:“あいうえお”
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 1}, ​​​​​(NSConcreteValue) NSRange: {1, 1}, ​​​​​(NSConcreteValue) NSRange: {2, 1}, ​​​​​(NSConcreteValue) NSRange: {3, 1}, ​​​​​(NSConcreteValue) NSRange: {4, 1}​​​}

set qRes to BridgePlus’s SMSForder’s rangesOfCharactersOfString:“This is a pen.”
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 1}, ​​​​​(NSConcreteValue) NSRange: {1, 1}, ​​​​​(NSConcreteValue) NSRange: {2, 1}, ​​​​​(NSConcreteValue) NSRange: {3, 1}, ​​​​​(NSConcreteValue) NSRange: {4, 1}, ​​​​​(NSConcreteValue) NSRange: {5, 1}, ​​​​​(NSConcreteValue) NSRange: {6, 1}, ​​​​​(NSConcreteValue) NSRange: {7, 1}, ​​​​​(NSConcreteValue) NSRange: {8, 1}, ​​​​​(NSConcreteValue) NSRange: {9, 1}, ​​​​​(NSConcreteValue) NSRange: {10, 1}, ​​​​​(NSConcreteValue) NSRange: {11, 1}, ​​​​​(NSConcreteValue) NSRange: {12, 1}, ​​​​​(NSConcreteValue) NSRange: {13, 1}​​​}

set rRes to BridgePlus’s SMSForder’s rangesOfLocalizedWordsOfString:“私の名前は長野谷です。”
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 1}, ​​​​​(NSConcreteValue) NSRange: {1, 1}, ​​​​​(NSConcreteValue) NSRange: {2, 2}, ​​​​​(NSConcreteValue) NSRange: {4, 1}, ​​​​​(NSConcreteValue) NSRange: {5, 2}, ​​​​​(NSConcreteValue) NSRange: {7, 1}, ​​​​​(NSConcreteValue) NSRange: {8, 2}​​​}

set sRes to BridgePlus’s SMSForder’s rangesOfSentencesOfString:“Hello. Goodby. See-you again!”
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 7}, ​​​​​(NSConcreteValue) NSRange: {7, 8}, ​​​​​(NSConcreteValue) NSRange: {15, 14}​​​}

set tRes to BridgePlus’s SMSForder’s rangesOfLocalizedSentencesOfString:“こんにちは。こんばんは。さようなら。えっ?!それはたいへんだ!! 「東京には空がない」と智恵子は言った。”
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 6}, ​​​​​(NSConcreteValue) NSRange: {6, 6}, ​​​​​(NSConcreteValue) NSRange: {12, 6}, ​​​​​(NSConcreteValue) NSRange: {18, 4}, ​​​​​(NSConcreteValue) NSRange: {22, 11}, ​​​​​(NSConcreteValue) NSRange: {33, 19}​​​}

set uRes to BridgePlus’s SMSForder’s rangesOfParagraphsOfString:“Hello. Goodby. See-you again!
Hello2. Goodby2. See-you again2!”

–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 29}, ​​​​​(NSConcreteValue) NSRange: {30, 32}​​​}

set vRes to BridgePlus’s SMSForder’s rangesOfLinesOfString:“Hello. Goodby. See-you again!
Hello2. Goodby2. See-you again2!”

–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {0, 29}, ​​​​​(NSConcreteValue) NSRange: {30, 32}​​​}

set wRes to BridgePlus’s SMSForder’s rangesOfString:“xxxx” inString:“#98158084 Xxxxx 98000000 xxxx” options:(current application’s NSCaseInsensitiveSearch)
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {10, 4}, ​​​​​(NSConcreteValue) NSRange: {25, 4}​​​}

set xRes to BridgePlus’s SMSForder’s rangesOfString:“ひよこ” inString:“ひょこっと登場したひよこだよびよこぴよこ” options:(current application’s NSCaseInsensitiveSearch) locale:(current application’s NSLocale’s localeWithLocaleIdentifier:“ja”)
–>  (NSArray) {​​​​​(NSConcreteValue) NSRange: {9, 3}​​​}

set yRes to BridgePlus’s SMSForder’s indexSetWithArray:{1, 2, 3, 4, 5}
–>  (NSIndexSet) [number of indexes: 5 (in 1 ranges), indexes: (1-5)]

–Miscellaneous methods

set theFiles to current application’s NSFileManager’s defaultManager()’s contentsOfDirectoryAtURL:(Cocoaify (path to desktop)) includingPropertiesForKeys:{current application’s NSURLContentModificationDateKey} options:(current application’s NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
set zRes to ASify from current application’s SMSForder’s resourceValueForKey:(current application’s NSURLContentModificationDateKey) forURLsOrFiles:theFiles
–>  {​​​​​date “2015年9月24日木曜日 15:02:34″, ​​​​​date “2015年9月18日金曜日 19:19:08″, ​​​​​date “2015年7月29日水曜日 19:08:31″, ​​​​​date “2015年7月20日月曜日 10:44:34″, ​​​​​date “2015年8月21日金曜日 16:49:28″, ​​​​​date “2015年7月30日木曜日 22:43:41″, ​​​​​date “2015年9月5日土曜日 13:15:02″, ​​​​​date “2015年9月29日火曜日 12:31:32″, ​​​​​date “2015年9月5日土曜日 13:28:11″, ​​​​​date “2015年7月30日木曜日 22:29:34″, ​​​​​date “2015年9月16日水曜日 9:33:31″, ​​​​​date “2015年5月30日土曜日 18:53:29″, ​​​​​date “2015年9月16日水曜日 8:57:55″, ​​​​​date “2015年9月12日土曜日 13:40:53″, ​​​​​date “2015年8月28日金曜日 11:42:57″, ​​​​​date “2015年7月14日火曜日 18:10:27″, ​​​​​date “2015年9月28日月曜日 17:12:39″, ​​​​​date “2015年6月19日金曜日 20:18:17″, ​​​​​date “2015年7月18日土曜日 17:35:20″, ​​​​​date “2015年5月10日日曜日 0:01:01″, ​​​​​date “2015年6月22日月曜日 15:46:16″, ​​​​​date “2015年9月16日水曜日 9:08:07″, ​​​​​date “2015年9月5日土曜日 13:14:35″, ​​​​​date “2015年9月16日水曜日 9:02:31″, ​​​​​date “2015年9月29日火曜日 0:11:38″, ​​​​​date “2015年8月18日火曜日 9:58:19″, ​​​​​date “2015年8月28日金曜日 21:23:26″, ​​​​​date “2015年7月18日土曜日 17:35:34″, ​​​​​date “2015年4月23日木曜日 15:21:54″, ​​​​​date “2015年6月3日水曜日 10:05:53″, ​​​​​date “2015年4月23日木曜日 15:21:56″, ​​​​​date “2015年9月26日土曜日 21:10:07″, ​​​​​date “2015年8月31日月曜日 17:29:15″, ​​​​​date “2015年9月22日火曜日 21:19:06″, ​​​​​date “2015年2月14日土曜日 0:30:28″, ​​​​​date “2015年8月24日月曜日 11:28:05″, ​​​​​date “2015年8月22日土曜日 16:52:07″, ​​​​​date “2015年8月14日金曜日 11:27:24″, ​​​​​date “2015年8月14日金曜日 11:27:23″, ​​​​​date “2015年8月14日金曜日 11:06:33″, ​​​​​date “2015年8月14日金曜日 13:13:16″, ​​​​​date “2015年8月31日月曜日 17:08:35″, ​​​​​date “2015年9月12日土曜日 15:45:52″, ​​​​​date “2015年6月22日月曜日 15:18:06″, ​​​​​date “2015年8月18日火曜日 10:01:56″, ​​​​​date “2015年5月21日木曜日 22:15:15″, ​​​​​date “2015年8月18日火曜日 10:06:21″, ​​​​​date “2015年8月20日木曜日 12:52:31″, ​​​​​date “2015年7月10日金曜日 16:01:16″, ​​​​​date “2015年7月14日火曜日 18:18:39″, ​​​​​date “2015年7月16日木曜日 15:22:20″, ​​​​​date “2015年7月16日木曜日 15:33:31″, ​​​​​date “2015年7月16日木曜日 17:01:33″, ​​​​​date “2015年7月18日土曜日 17:28:59″, ​​​​​date “2015年8月10日月曜日 23:13:55″, ​​​​​date “2015年8月14日金曜日 18:08:45″, ​​​​​date “2015年8月25日火曜日 18:47:58″, ​​​​​date “2015年9月8日火曜日 16:22:08″, ​​​​​date “2015年9月2日水曜日 15:04:11″, ​​​​​date “2015年9月6日日曜日 19:53:32″, ​​​​​date “2015年9月15日火曜日 20:49:36″, ​​​​​date “2015年9月21日月曜日 0:44:20″, ​​​​​date “2015年9月28日月曜日 18:45:49″, ​​​​​date “2015年9月28日月曜日 18:46:30″, ​​​​​date “2015年9月29日火曜日 10:38:05″, ​​​​​date “2015年9月29日火曜日 10:40:08″, ​​​​​date “2015年9月29日火曜日 12:29:06″, ​​​​​date “2015年9月29日火曜日 12:29:12″, ​​​​​date “2015年9月29日火曜日 13:53:04″, ​​​​​date “2015年9月21日月曜日 10:20:00″, ​​​​​date “2015年9月21日月曜日 8:55:56″, ​​​​​date “2011年12月29日木曜日 14:30:32″, ​​​​​date “2015年7月12日日曜日 17:53:35″, ​​​​​date “2015年5月19日火曜日 14:43:24″, ​​​​​date “2015年9月15日火曜日 18:30:44″, ​​​​​date “2015年9月29日火曜日 12:25:24″, ​​​​​date “2015年9月29日火曜日 12:27:06″, ​​​​​date “2015年9月29日火曜日 12:28:28″, ​​​​​date “2015年5月25日月曜日 15:59:07″, ​​​​​date “2015年5月25日月曜日 15:59:07″​​​}

★Click Here to Open This Script 

2015/09/28 IMの状態を取得・設定する

Shane StanleyのBridgePlus v1.2で新たに実装された「IMまわりの制御を行う機能」を用いて、IMの状態を取得・設定するAppleScriptです。Shane、本当にありがとう! Many Thanks!!

日本語Scripterの長年の懸案事項であった「日本語入力の状態の取得および設定」を、GUI Scriptingを使用せずに行えます。

fig0.png

自分もひそかに「Cocoaの機能を呼び出してできないか?」と独自に(NSTextInputContextあたりを)調べていた最中だったので、Shaneから「こんなのできたよ」と教えてもらった時には腰を抜かしました。

さまざまなアプリケーションの自動化を行うさいに、IMの状態取得と設定は欠くべからざる機能なのですが、GUI Scripting経由で強引に設定するやり方はいまひとつ信頼性が低く(Applet内から実行したときに効かないことがあるのはなんでだろー)、とてつもなく重要な機能の割に長年ASの標準機能に実装もされてきませんでした。

もう、オーストラリアに足を向けて寝られません。カナダ(Mark Alldritt)、アメリカのバーモント州(Bill Cheeseman)にも足を向けて寝られません(クパティーノに足を向けて寝るべき)。

ただ、実験したところ機能が「強力すぎる」点も見受けられ、たとえばフランス語を使わない設定になっている環境でフランス語のIMを呼び出すと・・・呼び出せるうえに、システム環境設定の「言語と地域」にいきなりフランス語が追加されます。このことはよーーく覚えておく必要があります(調子に乗って各種IMを呼び出しまくると、「言語と地域」にいろんな言語が追加されます)。

また、com.apple.inputmethod.ironwoodは音声入力に該当するようですが、これを呼び出しても音声認識による入力状態には移行しませんでした。惜しい、おしすぎる!(^ー^;;

fig1.png

fig2.png

fig3.png

fig4.png

AppleScript名:IMの状態を取得・設定する
– Created 2015-09-16 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePLus : script “BridgePlus” version “1.2″ –1.2より古いバージョン(1.1など)ではこの機能が存在しない

load framework

set y to current application’s SMSForder’s availableInputSourceIDs()
–>  (NSArray) {”com.apple.inputmethod.Kotoeri.Japanese”, “com.apple.inputmethod.Kotoeri.Roman”, “com.apple.inputmethod.Kotoeri.Japanese.Katakana”, “com.apple.inputmethod.Kotoeri”, “com.apple.50onPaletteIM”, “com.apple.inputmethod.ironwood”, “com.apple.CharacterPaletteIM”, “com.apple.KeyboardViewer”}

set y to current application’s SMSForder’s allAvailableInputSourceIDs()
–>  (NSArray) {”com.apple.inputmethod.Kotoeri.Japanese”, “com.apple.inputmethod.Kotoeri.Roman”, “com.apple.inputmethod.Kotoeri.Japanese.Katakana”, “com.apple.inputmethod.Kotoeri”, “com.apple.50onPaletteIM”, “com.apple.inputmethod.ironwood”, “com.apple.CharacterPaletteIM”, “com.apple.KeyboardViewer”, “com.apple.keylayout.US”, “com.apple.SyntheticRomanMode”, “com.apple.keylayout.Czech-QWERTY”, “com.apple.keylayout.Czech”, “com.apple.keylayout.Estonian”, “com.apple.keylayout.Hungarian-QWERTY”, “com.apple.keylayout.Hungarian”, “com.apple.keylayout.Latvian”, “com.apple.keylayout.Lithuanian”, “com.apple.keylayout.PolishPro”, “com.apple.keylayout.Polish”, “com.apple.keylayout.Slovak”, “com.apple.keylayout.Slovak-QWERTY”, “com.apple.keylayout.Bulgarian-Phonetic”, “com.apple.keylayout.Bulgarian”, “com.apple.keylayout.Byelorussian”, “com.apple.keylayout.Macedonian”, “com.apple.keylayout.Russian-Phonetic”, “com.apple.keylayout.Russian”, “com.apple.keylayout.RussianWin”, “com.apple.keylayout.Serbian”, “com.apple.keylayout.Ukrainian-PC”, “com.apple.keylayout.Ukrainian”, “com.apple.keylayout.Colemak”, “com.apple.keylayout.Dvorak-Left”, “com.apple.keylayout.Dvorak-Right”, “com.apple.keylayout.Dvorak”, “com.apple.keylayout.DVORAK-QWERTYCMD”, “com.apple.keylayout.KANA”, “com.apple.keylayout.Australian”, “com.apple.keylayout.Austrian”, “com.apple.keylayout.Belgian”, “com.apple.keylayout.Brazilian-ABNT2″, “com.apple.keylayout.Brazilian”, “com.apple.keylayout.British-PC”, “com.apple.keylayout.British”, “com.apple.keylayout.Canadian-CSA”, “com.apple.keylayout.Canadian”, “com.apple.keylayout.Danish”, “com.apple.keylayout.Dutch”, “com.apple.keylayout.Finnish”, “com.apple.keylayout.French-PC”, “com.apple.keylayout.French-numerical”, “com.apple.keylayout.French”, “com.apple.keylayout.German”, “com.apple.keylayout.Irish”, “com.apple.keylayout.Italian-Pro”, “com.apple.keylayout.Italian”, “com.apple.keylayout.Norwegian”, “com.apple.keylayout.Portuguese”, “com.apple.keylayout.Spanish-ISO”, “com.apple.keylayout.Spanish”, “com.apple.keylayout.Swedish-Pro”, “com.apple.keylayout.Swedish”, “com.apple.keylayout.SwissFrench”, “com.apple.keylayout.SwissGerman”, “com.apple.keylayout.USInternational-PC”, “com.apple.keylayout.2SetHangul”, “com.apple.keylayout.AfghanDari”, “com.apple.keylayout.AfghanPashto”, “com.apple.keylayout.AfghanUzbek”, “com.apple.keylayout.Anjal”, “com.apple.keylayout.Arabic-AZERTY”, “com.apple.keylayout.Arabic-NorthAfrica”, “com.apple.keylayout.Arabic-QWERTY”, “com.apple.keylayout.Arabic”, “com.apple.keylayout.ArabicPC”, “com.apple.keylayout.Armenian-HMQWERTY”, “com.apple.keylayout.Armenian-WesternQWERTY”, “com.apple.keylayout.Azeri”, “com.apple.keylayout.Bangla-QWERTY”, “com.apple.keylayout.Bangla”, “com.apple.keylayout.CangjieKeyboard”, “com.apple.keylayout.Cherokee-Nation”, “com.apple.keylayout.Cherokee-QWERTY”, “com.apple.keylayout.Croatian”, “com.apple.keylayout.Croatian-PC”, “com.apple.keylayout.Devanagari-QWERTY”, “com.apple.keylayout.Devanagari”, “com.apple.keylayout.Faroese”, “com.apple.keylayout.FinnishExtended”, “com.apple.keylayout.FinnishSami-PC”, “com.apple.keylayout.Georgian-QWERTY”, “com.apple.keylayout.Greek”, “com.apple.keylayout.GreekPolytonic”, “com.apple.keylayout.Gujarati-QWERTY”, “com.apple.keylayout.Gujarati”, “com.apple.keylayout.Gurmukhi-QWERTY”, “com.apple.keylayout.Gurmukhi”, “com.apple.keylayout.Hawaiian”, “com.apple.keylayout.Hebrew-QWERTY”, “com.apple.keylayout.Hebrew”, “com.apple.keylayout.Hebrew-PC”, “com.apple.keylayout.Icelandic”, “com.apple.keylayout.Inuktitut-Nunavut”, “com.apple.keylayout.Inuktitut-Nutaaq”, “com.apple.keylayout.Inuktitut-QWERTY”, “com.apple.keylayout.InuttitutNunavik”, “com.apple.keylayout.IrishExtended”, “com.apple.keylayout.Jawi-QWERTY”, “com.apple.keylayout.Kannada-QWERTY”, “com.apple.keylayout.Kannada”, “com.apple.keylayout.Kazakh”, “com.apple.keylayout.Khmer”, “com.apple.keylayout.Kurdish-Sorani”, “com.apple.keylayout.Malayalam-QWERTY”, “com.apple.keylayout.Malayalam”, “com.apple.keylayout.Maltese”, “com.apple.keylayout.Maori”, “com.apple.keylayout.Myanmar-QWERTY”, “com.apple.keylayout.Nepali”, “com.apple.keylayout.NorthernSami”, “com.apple.keylayout.NorwegianExtended”, “com.apple.keylayout.NorwegianSami-PC”, “com.apple.keylayout.Oriya-QWERTY”, “com.apple.keylayout.Oriya”, “com.apple.keylayout.Persian-QWERTY”, “com.apple.keylayout.Persian”, “com.apple.keylayout.Persian-ISIRI2901″, “com.apple.keylayout.Romanian-Standard”, “com.apple.keylayout.Romanian”, “com.apple.keylayout.Sami-PC”, “com.apple.keylayout.Serbian-Latin”, “com.apple.keylayout.Sinhala-QWERTY”, “com.apple.keylayout.Sinhala”, “com.apple.keylayout.Slovenian”, “com.apple.keylayout.SwedishSami-PC”, “com.apple.keylayout.Tamil99″, “com.apple.keylayout.Telugu-QWERTY”, “com.apple.keylayout.Telugu”, “com.apple.keylayout.Thai-PattaChote”, “com.apple.keylayout.Thai”, “com.apple.keylayout.TibetanOtaniUS”, “com.apple.keylayout.Tibetan-QWERTY”, “com.apple.keylayout.Tibetan-Wylie”, “com.apple.keylayout.Turkish-QWERTY-PC”, “com.apple.keylayout.Turkish-QWERTY”, “com.apple.keylayout.Turkish”, “com.apple.keylayout.USExtended”, “com.apple.keylayout.UnicodeHexInput”, “com.apple.keylayout.Urdu”, “com.apple.keylayout.Uyghur”, “com.apple.keylayout.Vietnamese”, “com.apple.keylayout.Welsh”, “com.apple.keylayout.ZhuyinBopomofo”, “com.apple.keylayout.390Hangul”, “com.apple.keylayout.3SetHangul”, “com.apple.keylayout.GJCRomaja”, “com.apple.keylayout.HNCRomaja”, “com.apple.keylayout.WubihuaKeyboard”, “com.apple.keylayout.WubixingKeyboard”, “com.apple.keylayout.ZhuyinEten”, “com.apple.inputmethod.PluginIM”, “com.apple.inputmethod.Ainu”, “com.apple.inputmethod.ink.inkserver”, “com.apple.inputmethod.Korean”, “com.apple.PressAndHold”, “com.apple.inputmethod.SCIM”, “com.apple.inputmethod.AssistiveControl”, “com.apple.inputmethod.Tamil”, “com.apple.inputmethod.TCIM”, “com.apple.inputmethod.ChineseHandwriting”, “com.apple.inputmethod.VietnameseIM”, “com.apple.inputmethod.AinuIM.Ainu”, “com.apple.inputmethod.Kotoeri.Japanese.HalfWidthKana”, “com.apple.inputmethod.Kotoeri.Japanese.FullWidthRoman”, “com.apple.inputmethod.Korean.3SetKorean”, “com.apple.inputmethod.Korean.2SetKorean”, “com.apple.inputmethod.Korean.HNCRomaja”, “com.apple.inputmethod.Korean.390Sebulshik”, “com.apple.inputmethod.Korean.GongjinCheongRomaja”, “com.apple.inputmethod.SCIM.ITABC”, “com.apple.inputmethod.SCIM.WBH”, “com.apple.inputmethod.SCIM.WBX”, “com.apple.inputmethod.Tamil.AnjalIM”, “com.apple.inputmethod.Tamil.Tamil99″, “com.apple.inputmethod.TCIM.WBH”, “com.apple.inputmethod.TCIM.Zhuyin”, “com.apple.inputmethod.TCIM.Cangjie”, “com.apple.inputmethod.TCIM.ZhuyinEten”, “com.apple.inputmethod.TCIM.Jianyi”, “com.apple.inputmethod.TCIM.Pinyin”, “com.apple.inputmethod.VietnameseIM.VietnameseSimpleTelex”, “com.apple.inputmethod.VietnameseIM.VietnameseVNI”, “com.apple.inputmethod.VietnameseIM.VietnameseVIQR”, “com.apple.inputmethod.VietnameseIM.VietnameseTelex”, “jp.monokakido.inputmethod.Kawasemi”, “com.parallels.inputmethod.ParallelsIM”, “com.visionobjects.Axiotron Quickscript.TSC”, “jp.monokakido.inputmethod.Kawasemi.Japanese”, “jp.monokakido.inputmethod.Kawasemi.Japanese.HalfWidthKana”, “jp.monokakido.inputmethod.Kawasemi.Japanese.HalfWidthRoman”, “jp.monokakido.inputmethod.Kawasemi.Roman”, “jp.monokakido.inputmethod.Kawasemi.Japanese.FullWidthRoman”, “jp.monokakido.inputmethod.Kawasemi.Japanese.Katakana”, “jp.monokakido.inputmethod.Kawasemi.Japanese.Code”}

–デモ: IMのメニューで「A」と「あ」を交互に切り替える
repeat 10 times
  set x to current application’s SMSForder’s changeInputSourceTo:“com.apple.inputmethod.Kotoeri.Roman”
  
tell current application
    delay 0.5
  end tell
  
set x to current application’s SMSForder’s changeInputSourceTo:“com.apple.inputmethod.Kotoeri.Japanese”
  
tell current application
    delay 0.5
  end tell
end repeat

★Click Here to Open This Script 

AppleScript名:ironwood(音声認識入力)を呼び出したが、音声入力は始まらなかった
– Created 2015-09-16 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePLus : script “BridgePlus” version “1.2″ –1.2より古いバージョン(1.1など)ではこの機能が存在しない

load framework

tell application “TextEdit” to activate
delay 3
set x to current application’s SMSForder’s changeInputSourceTo:“com.apple.inputmethod.ironwood”

★Click Here to Open This Script 

2015/09/14 地域、事業所名のリストを出現頻度カウントなどの処理を行って整形

家族から頼まれて作成したプログラムで、割と手こずったデータ処理のAppleScriptです。

Excelに入っているデータを読み取ってAppleScript側で集計処理を行ってみました。Pure AppleScript的な処理とCocoa的な処理がいろいろと入り混じっており、逆に効率が悪くなっているかもしれません(ただし、BridgePlusを活用しまくり、Cocoaの機能を利用して処理を書きまくっているので、処理速度は爆速)。

ここに掲載したScriptは、その処理の一部ですが・・・単体で実行可能なものです。

{「営業所エリア名」、「事業所名」}のペアになっているリストで頻度集計を行うというものですが、同時に「営業所エリア名」を「エリア名略称」に変換します。

Input:{{”西営業所”, “◯◯◯◯◯”}, {”四国営業所”, “●●●●”}, {”西営業所”, “◯◯◯◯◯”}}

Output:
(XXXX西部)◯◯◯◯◯ (2)
(XXXX四国)●●●●

非常に些細な処理ですが、実際に組んでみるとそれなりに手間がかかりました。

NSCountedSetにNSDictionaryを突っ込んで頻度カウントができるんじゃないかと思って試行錯誤してみたのですが、なかなかうまくいきませんでした(どうやら突っ込んでカウントはできているものの、値を取り出すのに失敗)。まだ、なかなか難しいです(汗)。

AppleScript名:地域、事業署名のリストを出現頻度カウントなどの処理を行って整形v2
– Created 2015-09-13 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”

load framework

set oList to {{“西営業所”, “◯◯◯◯◯”}, {“四国営業所”, “●●●●”}, {“西営業所”, “◯◯◯◯◯”}}

set aRes to formatBranchDescriptions(oList)
–>
(*
“(XXXX西部)◯◯◯◯◯ (2)
(XXXX四国)●●●●

*)

on formatBranchDescriptions(oList)
  set labelList to {“branchName”, “elipName”}
  
set aList to BridgePlus’s sublistsIn:oList asDictionariesUsingLabels:labelList
  
  
—set aList to {{branchName:”西営業所”, elipName:”◯◯◯◯◯”}, {branchName:”四国営業所”, elipName:”●●●●”}, {branchName:”西営業所”, elipName:”◯◯◯◯◯”}}
  
  
set anArray to Cocoaify aList
  
  
set elipNameList to (anArray’s valueForKey:“elipName”) as list
  
–>  {”◯◯◯◯◯”, “●●●●”, “”◯◯◯◯◯”}
  
set eList to uniquify1DList(elipNameList) of me –ユニーク化
  
  
set outStr to “”
  
repeat with i in eList
    set j to contents of i
    
set aRes to countSpecifiedItem(elipNameList, {j}) of me
    
    
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“elipName == %@”, j)
    
set aRec to (anArray’s filteredArrayUsingPredicate:aPredicate)
    
set anItem to branchName of first item of aRec
    
set eName2 to convBranchToElip(anItem)
    
if aRes > 1 then
      set outStr to outStr & {eName2 & j & “ (” & aRes & “)”} & return
    else
      set outStr to outStr & {eName2 & j} & return
    end if
  end repeat
  
return outStr
end formatBranchDescriptions

–リスト中の指定項目の出現回数を返す
on countSpecifiedItem(aList, countItem)
  set aRes to BridgePlus’s indexesOfItems:countItem inList:aList inverting:false
  
set aCount to length of aRes
  
return aCount
end countSpecifiedItem

–1D Listをユニーク化
on uniquify1DList(theList as list)
  set aArray to current application’s NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
set bList to ASify from bArray as list
  
return bList
end uniquify1DList

–営業所エリア名、エリア名略称変換
on convBranchToElip(aBranch)
  set aList to {{branchName:“東京営業所”, elipName:“(XXXX東京)”}, {branchName:“千葉営業所”, elipName:“(XXXX千葉)”}, {branchName:“神奈川営業所”, elipName:“(XXXX神奈川)”}, {branchName:“西営業所”, elipName:“(XXXX西部)”}, {branchName:“四国営業所”, elipName:“(XXXX四国)”}, {branchName:“茨城営業所”, elipName:“(XXXX茨城)”}}
  
set aDic to Cocoaify aList
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“branchName == %@”, aBranch)
  
set aRes to (aDic’s filteredArrayUsingPredicate:aPredicate)
  
try
    set anItem to contents of first item of (aRes as list)
    
set eName to (elipName of anItem)
  on error
    set eName to “”
  end try
  
return eName
end convBranchToElip

★Click Here to Open This Script 

2015/09/11 与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げ

Cocoaの機能を用いて、与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げするAppleScriptです。Shane StanleyのAppleScript Libraryである「BridgePlus」(フリー)のインストールが必要です。

こういうのがやりたくて、地道に部品を揃えてきたわけで・・・やらないわけにはいきません。

ただし、思ったよりも泥沼に足を突っ込みかけました。食後のハイキングのはずが、雪山で遭難しかけたという印象です。

NSSpeechSynthesizerのavailableVoices()は「インストールされていないTTS Voice」も返してくるため、せっかくテキストの言語を特定して、TTS Voiceを取得しても・・・インストールされていないTTS Voiceが返ってくるとお手上げです(T_T)。

tts_voice_list.png

英語だとNSLinguisticTaggerによる判定で”en”が、フランス語だと”fr”が返ってきますが、”en”はあくまで英語を示すものであり、”en_GB”もあれば”en_US”もあれば、”en_IN”(インド)、”en-scotland”(スコットランド)、”en_ZA”(南アフリカ)もあるわけです(自分も物好きですが、南アフリカのTTS Voiceはインストールしていません)。

“en”をキーにして、全TTS Voiceから言語コードを取得し、最初にヒットした言語コードが”en_ZA”であれば南アフリカのTTS Voiceでsayコマンドの実行を行おうとしてしまうので、”en”を”en-US”に置き換えるような、つじつまあわせの処理が必要になってしまいました(もう少しうまいやり方もありそうですが、食後の散歩のレベルを超えてしまいますので)。

実際に実戦レベルのAppleScriptを書いてみるまで、見えてこない「落とし穴」もあるので、こうして細かいScriptを書いておくという作業はなかなかあなどれません、、、

AppleScript名:与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げ
– Created 2015-09-11 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use BridgePlus : script “BridgePlus”

load framework

set aRes to getVoiceLanguageFromTTSVoices() –Language Code

–English, French, Japanese, Chinese
set cList to {“With the new MacBook, we set out to do the impossible.”, “Le nouveau MacBook est le résultat d’un défi presque impossible”, “新しいMacBookとともに、私たちは不可能だと思えることに挑みました。”, 于全新 MacBook,我们给自己定了一个几乎不可能实现的目}

–各言語テキストでループ
repeat with i in cList
  set j to contents of i
  
set aLang to specifyLanguageOfText(j) –テキストから言語コードを取得
  
  
–つじつまあわせ。ちょっとカッコ悪い
  
–(NSSpeechSynthesizerのavailableVoicesでインストールされていないVoiceまで返ってくることへの対策)
  
if aLang = “en” then
    set aLang to “en-US”
  else if aLang = “fr” then
    set aLang to “fr-FR”
  else if aLang = “ja” then
    set aLang to “ja-JP”
  end if
  
  
–全ボイス中から言語をサポートしているものを抽出
  
repeat with ii in aRes
    set jj to contents of ii
    
    
if (jj as text) is equal to (aLang as text) then
      
      
–最初にヒットした言語コードでPremium Voiceを検索
      
set v1fList to getPremiumVoiceNameWithFiltering(“VoiceGenderFemale”, jj)
      
set v1mList to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, jj)
      
set v1List to v1fList & v1mList
      
      
if v1List = {} then
        –Premium Voiceでヒットしなかった場合に、すべてのVoiceを検索
        
set v2fList to getAllVoiceNameWithFiltering(“VoiceGenderFemale”, jj)
        
set v2mList to getAllVoiceNameWithFiltering(“VoiceGenderMale”, jj)
        
set v2List to v2fList & v2mList
        
        
if v2List = {} then
          display dialog “この言語(” & jj & “)をサポートするTTS Voiceはインストールされていません。” –Error
          
exit repeat
        else
          copy v2List to v1List
        end if
        
      end if
      
      
repeat with iii in v1List
        set aVoice to contents of iii
        
try
          –総当たりでSayコマンド実行(インストールされていないTTS Voiceも取得できてしまうため)
          
tell current application
            say j using aVoice
          end tell
          
exit repeat
        end try
      end repeat
      
      
exit repeat
    end if
  end repeat
  
end repeat

–Premium Voices Only
on getPremiumVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLanguage == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@”, voiceLang, voiceGender, “premium”)
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getPremiumVoiceNameWithFiltering

–All Voices (VoiceLanguageで抽出)
on getAllVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLanguage == %@ and VoiceGender == %@”, voiceLang, voiceGender)
  
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getAllVoiceNameWithFiltering

on getLocaleIdentifierFromTTSVoices()
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLocaleIdentifier”)
  
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
end getLocaleIdentifierFromTTSVoices

on getVoiceLanguageFromTTSVoices()
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLanguage”)
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
end getVoiceLanguageFromTTSVoices

on specifyLanguageOfText(aStr)
  set aNSstring to current application’s NSString’s stringWithString:aStr
  
set tagSchemes to current application’s NSArray’s arrayWithObjects:(current application’s NSLinguisticTagSchemeLanguage)
  
set tagger to current application’s NSLinguisticTagger’s alloc()’s initWithTagSchemes:tagSchemes options:0
  
tagger’s setString:aNSstring
  
set aLanguage to tagger’s tagAtIndex:0 |scheme|:(current application’s NSLinguisticTagSchemeLanguage) tokenRange:(missing value) sentenceRange:(missing value)
  
return aLanguage as text
end specifyLanguageOfText

–1D Listをユニーク化してソート
on uniquifyAndSort1DList(theList as list, aBool as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“compare:”
  
set cArray to bArray’s sortedArrayUsingDescriptors:{aDdesc}
  
set bList to (ASify from cArray) as list
  
return bList
end uniquifyAndSort1DList

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

★Click Here to Open This Script 

2015/08/26 ASOCで性別と言語コードを指定してTTS voiceを取得 v2

Cocoaの機能を用いて、Text To Speechの音声から性別と言語を指定して、TTS Voice名称をリストで返すAppleScriptです。最初のバージョンに対して、「Premium」voiceに限定しないvoice名取得ルーチン「getAllVoiceNameWithFiltering」を追加しています。ほかに、機能的な差異はありません。

前バージョンに対し、Shane StanleyからNSPredicateの指定について、

set aStr to current application’s NSString’s stringWithString:(”VoiceLocaleIdentifier == ‘” & voiceLang & “‘ and  VoiceGender == ‘” & voiceGender & “‘ and VoiceIdentifier ENDSWITH[c] ‘premium’”)
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aStr

って、書いてあるけど引数をNSPredicateの指定文字列から分離したほうがクォートで囲む囲まないで悩まなくていいから、分離しといたほうがいいよ(意訳)という指摘があって・・・実際、NSPredicate以外のコードはすぐに書けたものの・・・クォートの指定でけっこう試行錯誤して悩んでいたので、まったくその通り(^ー^;;;;

set aPredicate to current application’s NSPredicate’s predicateWithFormat_(”VoiceLocaleIdentifier == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@”, voiceLang, voiceGender, “premium”)

とか(ここで「predicateWithFormat_」とアンダースコアで書かないと構文確認をパスできないところにASOCの闇と、Shaneの試行錯誤を感じます、、、)、

set aPredicate to current application’s NSPredicate’s predicateWithFormat:”VoiceLocaleIdentifier == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@” argumentArray:{voiceLang, voiceGender, “premium”}

みたいに書いたほうがシンプルでいいよ的な話で、まさにそのとおり。で、いろいろ書き換えておきました。

各TTS Voiceについては、性別、言語、国などのほかに「年齢」というパラメータも持っているのですが、年齢を条件にして綿密に絞り込む・・・というほどには、TTS Voiceの数が多くないので、年齢は考慮していません。

これ(↑)を掲載するまでに、「クリップボードの内容をスタイル付きテキストで取得する方法」について調べ、調べきれなかったので、do shell script使いまくりの昔作ったAppleScriptでHTMLに書き出した、というオチもありました。Cocoaのクリップボード関連、普段使わないうえに、いざ真剣に調べだしてもあんまりまとまったまともでわかりやすい情報がなくて困りものです。

AppleScript名:ASOCで性別と言語コードを指定してTTS voiceを取得 v2
– Created 2015-08-25 by Takaaki Naganoya
– Modified 2015-08-26 by Shane Stanley, Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use BridgePlus : script “BridgePlus”

load framework

set v1Res to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, “ja_JP”)
–>  {​​​​​”Otoya”​​​}

set v2Res to getPremiumVoiceNameWithFiltering(“VoiceGenderFemale”, “en_US”)
–>  {​​​​​”Allison”, ​​​​​”Ava”, ​​​​​”Jill”, ​​​​​”Samantha”​​​}

set v3Res to getAllVoiceNameWithFiltering(“VoiceGenderFemale”, “en_US”)
–>  {​​​​​”Agnes”, ​​​​​”Allison”, ​​​​​”Ava”, ​​​​​”Jill”, ​​​​​”Kathy”, ​​​​​”Princess”, ​​​​​”Samantha”, ​​​​​”Vicki”, ​​​​​”Victoria”​​​}

–Premium Voices Only
on getPremiumVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list