Archive for the 'Application Control' Category

2017/09/19 Notesで選択中のnoteやfolderの情報を取得する?

defaultsコマンド経由でNotes.app(日本語ローカライズ名:メモ.app)で選択中のnoteやfolderの情報を取得する(参考になるかもしれない)AppleScriptです。

selectionとかselected folderなどの用語が用意されていないことでおなじみのNotes.app。「現在表示中のノート」を処理したいのはやまやまですが、用語辞書に書かれていない機能は、まっとうな手段では呼び出せません。

となると、「まっとうでない方法」を検討することになります。

まっとうでない手段を選ぶことで、スピード低下、安定性のなさ、メンテナンスの手間など負の側面と向き合う必要が出てきますが、問題自体の解決は行えます。

(1)GUI Scripting
まっとうでない方法の筆頭。画面上から無理やり情報を取得して、選択状態にあるGUI部品のタイトルを取得することで、選択中のノートの情報を特定できそうです。興味と必要性がないので深追いしていませんが、Notes.appの各種表示状態(アカウント情報一覧を表示するとか、表示を隠すとか)に合わせて数パターンの処理を用意しておくことで、実現できるメドは立っています。

ただし、GUI Scriptingのつねとして、「スピードは通常のAppleScriptの100倍以上遅い」(Cocoa経由の処理と比較すると数万倍遅い)「予期しないアプリケーション側の表示には追従できない」「OSやアプリケーションのバージョンアップにともない、表示内容やGUI部品の階層が変わったぐらいで書き換えが必要になる」ということから、多用は避けたいところです。

(2)defaultsコマンド
まっとうでない方法として有名。アプリケーションの設定情報を直接読んで処理します。アプリケーションやOSがバージョンアップして内部情報が変更されると動かなくなるのはGUI Scripting同様。

defaultsコマンドから得られる情報をAppleScriptネイティブのデータ形式(recordとか)に変換するのが面倒でしたが、Cocoaの機能を利用して割と手軽に読み込めるようになってきました。

Terminal上で、

  defaults read com.apple.Notes

を実行してみたところ、設定情報の中に現在選択中のnoteおよびfolderを記録している箇所があったので、実際にAppleScript中に記述して取り出してみました。

今回掲載したのが、そのdefaultsコマンドを利用した情報取得のサンプルです。このデータをもとにSQLiteのデータベースを検索してみればいいかも、などと思ってデータベースを漁ってみたものの、

notes_sqlite3_resized.png

defaultsから得られたような形式のID(UUIDっぽい?)はDB中に記録されていないようで、ちょっと残念です(SQLiteではなくCoreData経由で調べるべき?)。もう少し調べてみると手がかりが見つかるかもしれませんが、出先の電車の中で調べた程度だったのでこのぐらいです。

AppleScript名:Notesで選択中のnoteやfolderの情報をdefaults経由で取得する
– Created 2016-10-31 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4827

set sRes to do shell script ” defaults read com.apple.Notes ArchivedUIState”
set aDict to deserializeToPlistString(sRes) of me

set curNote to (aDict’s valueForKey:“currentNote”) as string
–>  ”860327F4-4E6F-44FB-92EC-720B47CDD38A”

set curNoteFol to (aDict’s valueForKey:“currentNoteFolder”) as string
–>  ”184daff620aca4ad23fd2bf70d0b76af”

set folderListHidden to (aDict’s valueForKey:“folderListHidden”) as integer as boolean
–>  false

return {curNote, curNoteFol, folderListHidden}

–XML-format plist string–> list or record
on deserializeToPlistString(aStr as string)
  set deStr to current application’s NSString’s stringWithString:aStr
  
set theData to deStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aList to current application’s NSPropertyListSerialization’s propertyListWithData:theData options:(current application’s NSPropertyListMutableContainersAndLeaves) |format|:(missing value) |error|:(missing value)
  
return aList
end deserializeToPlistString

★Click Here to Open This Script 

2017/09/15 Path Finderの最前面のウィンドウで選択中のアイテムをaliasのリストで返す

Finder代替アプリケーション「Path Finder」で最前面のウィンドウので選択中のファイル情報をaliasのlistで取得するAppleScriptです。

Cocoa Finderのパフォーマンスの低さに愛想が尽きたので、Finder代替ツールを調べていたら、Path Finder一択っぽいのでScript対応機能を調べてみたものです。

AppleScript名:Path Finderの最前面のウィンドウで選択中のアイテムをaliasのリストで返す
– Created 2017-09-15 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4826

set aList to getSelectionFromFrontmostPathFinderWindow() of me
–> {alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内にXML要素を作成して、移動.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内にXML要素を作成して、複製.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内のXML要素を削除.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、指定ScriptLabelの中のテキストの、指定行の指定文字以降を行末まで取得.scpt”}

on getSelectionFromFrontmostPathFinderWindow()
  tell application “Path Finder”
    set wCount to count every finder window
    
if wCount = 0 then return false
    
    
set aSel to selection
    
if aSel = missing value then return false
    
    
set newList to {}
    
repeat with i in aSel
      set j to (path of i) as alias
      
set the end of newList to j
    end repeat
  end tell
  
return newList
end getSelectionFromFrontmostPathFinderWindow

★Click Here to Open This Script 

2017/09/15 Path Finderの最前面のウィンドウの情報を取得する

Finder代替アプリケーション「Path Finder」で最前面のウィンドウの情報を取得するAppleScriptです。

Cocoa Finderのパフォーマンスの低さに愛想が尽きたので、Finder代替ツールを調べていたら、Path Finder一択っぽいのでScript対応機能を調べてみました。

WindowをWindowと指定してはダメで、Finder Windowというオブジェクトでアクセスできることを知りました(初見なので)。

Finderと同様にWindow(正確に表現するとFinder Window)のtargetを取得できるのですが、このtargetがPath Finder独自のオブジェクト階層で表現されているのでそのままでは他のアプリケーションでは(パス情報を)使えません。

Finderオブジェクト Path Finderオブジェクト
file fsFile
folder fsFolder
disk disk

そこで、Finder Windowのtargetからpath(文字列)を取り出してaliasにcastしています。

Path Finder自体にはあまり美的センスを感じていないし、思ったよりも機能拡張を行うための機構がないため、「これではない」感じがしています。

AppleScript名:Path Finderの最前面のウィンドウの情報を取得する
– Created 2017-09-15 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4825

tell application “Path Finder”
  set wCount to count every finder window
  
if wCount = 0 then return false
  
  
tell finder window 1
    properties
    
–> {zoomable:true, closeable:true, zoomed:false, class:finder window, index:2, visible:true, name:”InDesign CS3″, current view:”list view”, miniaturizable:true, target:fsFolder “InDesign CS3″ of fsFolder “各種アプリケーション、Application Specific” of fsFolder “AppleScript” of fsFolder “Documents” of fsFolder “me” of fsFolder “Users” of disk “Macintosh HD” of application “Path Finder”, id:89809, miniaturized:false, resizable:true, bounds:{36, 27, 1029, 1111}, document:missing value}
    
    
set aTarget to (path of target) as alias
    
–> alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:”
  end tell
end tell

★Click Here to Open This Script 

2017/09/15 64ビット化して処理能力が低下したCocoa Finderの代替ツールはSystem Events?

macOS 10.6〜10.7で64ビット化にともないCocoaで書き直されたFinderのパフォーマンス低下が(当時から)指摘されていましたが、日常的な用途ではあまり気にしていませんでした。

Cocoa Finderのはじめのうちは、OS起動直後に表示されるFinderのウィンドウ内のアイコン描画が間に合わないのか仮アイコンで表示され、徐々に正しいアイコンで表示されるような光景もよく目にしていました(さすがに最近はそういう光景は目にしなくなりましたけれども)。

そんな中、macOS 10.13beta+MacBook Air 2011で大量のファイルを処理したときに、Finder経由で処理するとトンでもなくスピードが(SSDなのに)遅いことに気づきました。SSDに合わせて再設計したAPFS+SSDの組み合わせなのに、とてつもなく遅い。速い速いというふれ込みだったのに、Finder経由だと驚くほど遅い。

最初は、AppleScriptとFinderの間のやりとりが遅いものだと思っていましたが、実はFinderの処理そのものが遅いことが判明。数百個のファイルを選択してFinder上で(メニューから「複製」コマンドを実行して)ファイルコピーさせたりすると、めまいがするほど低速です。

そして、Appleの「AppleScript Language Guide」で「list folder」コマンド(廃止予定)を調べていたら、同コマンドの代替としてSystem Events経由でファイル情報にアクセスするやり方が掲載されているのを見つけ、Finder経由のファイル処理がすでに推奨されていない状態なのではないか、と疑いを持つようになりました。

そこで、同じファイル処理を(macOS 10.13beta上で)System EventsとFinderに対して実行してみたところ、4,224ファイル存在しているフォルダから、全ファイル名を取得する処理は、

 System Events:1秒
 Finder:6秒

同フォルダに対して、ファイル名に「99」という文字を含むファイル名だけを抽出させてみた(フィルタ参照)ところ、

 System Events:14秒
 Finder:計測不能(timeout時間を3,600秒に設定して実行してみたものの、Finderがハングアップして処理が数十分返ってこない)

といったところ。速度、信頼性ともにFinderよりもSystem Eventsの方が高いという状況です。処理対象ファイル数が100以下ぐらいであればFinder経由でも気にならないのですが、それを超えると露骨にパフォーマンス低下が顕在化します。

ただし、最近はAppleScriptでもCocoaの機能(NSFileManager)を利用してファイルの処理を行なっていたので、System Events経由のファイル処理も「驚くほど速いわけでもない」という印象。指定フォルダ内のファイルの抽出もSpotlight経由で行なっていたので、それほど不自由は感じていませんでした。

サンプルを掲載する際にも、なるべくSystem EventsかNSFileManagerを経由してファイル処理を行うようにする考えです。

Finderのコントロールは、選択中のフォルダやファイルを取得するとか、処理結果のフォルダやファイルを新規Finderウィンドウで表示させるといった用途に限定することが望ましいのでしょう(あと、指定フォルダ内のファイル一覧をas alias listで取得するのも(フィルタ参照を併用しなければ)高速です)。

2017/09/13 Safariの最前面のウィンドウの内容をすべて取得してTextEditで新規ドキュメントを作成

Safariで表示中の最前面のウィンドウのURLを取得して本文文字列を抽出し、テキストエディットの新規書類としてオープンするAppleScriptです。

たまに、Blog上の情報を部分的に利用したいようなときに、テキスト内容をコピー&ペーストで別のアプリケーションに持ってきますが、たまにテキストの選択ができないBlogに遭遇することがあります。

こんなとき、SafariのAppleScript用語辞書にはdocumentからtextが取得できるということになっていますが、目下Safari 10.1.2上のこの機能にはアクセスできません。SafariのAppleScript用語辞書はSafari 10.xになってからdocumentのpropertiesが取得できない(エラーになる)など、いまひとつです。

さらに、もっと前からdocumentのtextが取得できないようになっているので、とりあえずバグレポートで文句を書きつつ、対処してみました(Appleがバグ修正するのを待つよりも自前でScript書いた方が早いという)。

Safariの最前面のウィンドウ(frontmost document)から表示中のURLだけ取得してくれば、AppleScript単体でWeb内容のダウンロードからテキストの抽出までできるので、そのとおりに処理。取得しただけだとスクリプトエディタ上でログ表示(コピー可)するだけなので、とりあえずテキストエディットの新規書類を作成してその本文にテキストを入れてみました。

ちなみに、本Blog掲載のプログラムリストの末尾に「スクリプトエディタに内容を転送するリンク」をつけていますが、これはコピー&ペーストではなく、カスタムURLプロトコル(applescript://)経由でプロセス間通信によりWebブラウザからスクリプトエディタにデータ転送する仕組みを利用しています。一般ユーザーにとっては、「コピー&ペーストではない」という点がなかなか理解できないようです。

AppleScript名:Safariの最前面のウィンドウの内容をすべて取得してTextEditで新規ドキュメントを作成
– Created 2017-09-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4823

property NSAttributedString : a reference to current application’s NSAttributedString
property NSData : a reference to current application’s NSData
property |NSURL| : a reference to current application’s |NSURL|

–Get URL from frontmost window (document)
tell application “Safari”
  if (every document) = {} then return
  
tell front document
    set aURLstr to URL
  end tell
end tell

–Get String from Safari URL
set sRes to getStringFromRemoteHTML(aURLstr) of me

–Make new document with TextEdit to browse or edit text contents
tell application “TextEdit”
  make new document with properties {text:sRes}
  
activate
end tell

on getStringFromRemoteHTML(aURLstr as string)
  set theURL to |NSURL|’s URLWithString:aURLstr
  
set theData to NSData’s dataWithContentsOfURL:theURL
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
if attStr = missing value then error “Internet Connection lost or Wrong URL”
  
return (attStr’s |string|()) as string
end getStringFromRemoteHTML

★Click Here to Open This Script 

2017/09/13 macOS 10.13, High Sierra 9/26リリース、iTunes 12.7

macOS 10.13, High Sierraが日本時間で2017/9/26にリリースされることが発表されました。巨大なバグはバグではなく仕様として開き直って、リリース後のアップデートでなんとかしようという話のようです(日本語音声読み上げバグは直らないことが決定)。

リリース日が予想よりも1か月早いですが、これには9〜10月に新ハードウェア(iMac Proとか)をリリースするための措置でしょう。あるいは、iOSデバイスをTouch bar化するApple純正アプリケーション(サードパーティ製は1年前から存在していた)をiOS11用に提供し、macOSのアップデートとタイミングを揃える必要があったとか(願望をこめた予想)。

一方で、本日iTunes 12.7がリリースされました。High Sierraよりもこちらの変更のほうが変動幅が大きいかもしれません。

 ・着信音(ringtone)をMac上のiTunes上で扱わなくなった
 ・ポップアップメニューから「iTunes U」が削除された
 ・iOSデバイスのモバイルアプリストアが削除された

iTunes 12.7でAppleScript用語辞書の変更はありません(用語辞書だけで比較)。画面上で削除された機能について、iTunes 12.7のAppleScript用語内には、

media kind alert tone / audiobook / book / home video / iTunesU / movie / song / music video / podcast / ringtone / TV show / voice memo / unknown — the media kind of the track

と、定義が残ったままです(このあたりいつものApple仕事。善意に解釈できないこともないものの、善意に解釈するとバカを見るところ)。

iPhoneの発表会で噂どおりの顔認識機能(Face ID)を搭載したiPhone Xが登場しました。予想どおり赤外線カメラを搭載し(可視光線からの画像だけでは顔認識の精度を上げられないので)ドットプロジェクタを併用しているため、OSレベルでのソフトウェアによる顔認識能力が上がったわけではなさそうです。

2017/09/09 macOS 10.13betaのFinderのScriptingが遅い

macOS 10.13 High Sierraの検証をMacBook Air 2011(Core i5 RAM 4GB)で行なっていますが、この環境が非力なためなのか、Finderに対してファイル操作を行うとハングアップしたり遅くなったりするなどの問題が見られています。

このままリリースしたら問題になるレベルです。NSFileManager経由でファイル操作を行うと問題がないため、

 (1)Finderがメモリを余計に喰うようになっている
 (2)FinderのAppleScript向けの機能に問題がある
 (3)Beta版のソフトウェアのため、処理に冗長な部分がある(ログ記録など)

などの事態も考えられますが、現状では相当まずい状態です。おそらくRAM 8GBの環境であれば問題は少ないことも期待できますが、自分がテストに使っている構成だとFinderへのScriptingが使い物になりません。

→ 特定条件下(1フォルダ内のファイル数が多い。4,000ファイルで実験)でFinder自体の応答性が極端に低下するようにも見えます。つまり、FinderとASの間が問題なのではなく、Finder自体にパフォーマンス上の問題が?

Shane Stanleyにも実際のScriptをやりとりして検証してもらいましたが、やはり同様にフィルタ参照でファイルの抽出などを行うと極端にパフォーマンスが落ちることが確認されました。

2017/09/08 iTunes Library上のsongでライブラリへの追加年を集計してKeynote書類上にグラフ作成

iTunesのMusic Libraryで楽曲のライブラリへの追加年で集計を行ってKeynote上にグラフを作成するAppleScriptです。6,775曲が登録されている自分のライブラリで集計→グラフ作成で3秒程度です。

itunes_usic.png

iTunes LibraryへのアクセスをiTunesLibrary framework経由で行い、NSCountedSetで集計を行うことで高速に処理を行ないます。

iTunesの基礎的な操作については電子書籍「iTunes Control」にて、Keynoteのグラフ作成については、電子書籍「Keynote Control with AppleScript」 △脳楮戮望匆陲靴討い泙后6縮がある方はぜひお買い求めください。

年々、楽曲を聴かない&買わなくなっている様子が見てとれますが、ほかの人はどんなもんなのか興味があります。

AppleScript名:iTunes Library上のsongでライブラリへの追加年で集計してKeynote書類上にグラフ作成
– Created 2017-09-06 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “iTunesLibrary”
–http://piyocast.com/as/archives/4816

property NSString : a reference to current application’s NSString
property NSArray : a reference to current application’s NSArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSDictionary : a reference to current application’s NSDictionary
property NSCountedSet : a reference to current application’s NSCountedSet
property ITLibrary : a reference to current application’s ITLibrary
property NSMutableArray : a reference to current application’s NSMutableArray

set yRec to retSongAddedYear() of me
–>  {{theName:2005, numberOfTimes:1907}….}

–楽曲のiTunesライブラリへの追加「年」のみ抽出
set yList to ((NSArray’s arrayWithArray:yRec)’s valueForKeyPath:“theName”) as list
–>  {2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017}

–楽曲のiTunesライブラリへの追加「年」ごとのカウントを抽出
set vList to ((NSArray’s arrayWithArray:yRec)’s valueForKeyPath:“numberOfTimes”) as list
–>  {1907, 1125, 853, 319, 638, 353, 351, 241, 605, 344, 71, 76, 28}

tell application “Keynote”
  set adoc to (make new document with properties {document theme:theme “ホワイト”, height:768, width:1024}) –Caution: theme name is **localized** (”White”)
  
tell front document
    set base slide of current slide to master slide “空白” –Caution: master slide name is **localized** (”Blank”)
    
tell current slide
      add chart row names {“ライブラリ追加年”} column names yList data {vList} type vertical_bar_2d group by chart row –row name is “iTunes Library Added Year”
    end tell
  end tell
  
activate
end tell

on retSongAddedYear()
  set library to ITLibrary’s libraryWithAPIVersion:“1.0″ |error|:(missing value)
  
if library is equal to missing value then return {}
  
  
set allTracks to library’s allMediaItems()
  
set allCount to allTracks’s |count|()
  
  
set anEnu to allTracks’s objectEnumerator()
  
set newArray to NSMutableArray’s alloc()’s init()
  
  
repeat
    set aPL to anEnu’s nextObject()
    
if aPL = missing value then exit repeat
    
try
      set aKind to (aPL’s mediaKind) as integer
      
if (aKind as integer) is equal to 2 then –Music, Song
        set pMonth to ((aPL’s addedDate() as date)’s year) as integer
        
newArray’s addObject:(pMonth)
      end if
    on error
      set aLoc to (aPL’s location’s |path|()) as string
    end try
  end repeat
  
  
return countItemsByItsAppearance(newArray) of me
end retSongAddedYear

–出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to NSSortDescriptor’s sortDescriptorWithKey:“theName” ascending:true
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

★Click Here to Open This Script 

2017/09/07 小説家になろうサイトでダウンロード対象作品テキストを全話ダウンロード

「小説家になろう」サイトに掲載中の小説の「テキストをダウンロード」ウィンドウを表示させた状態で実行すると、全話ダウンロードするAppleScriptです。「Knight’s & Magic」を全話ダウンロードするために作ったものです。

典型的な「作り捨て」Scriptですが、別に「Knight’s & Magic」向けにカスタマイズしているわけではないので、同サイト掲載中のすべての作品で使えるはずです。

最初に1回ポップアップボタンをクリックしてメニューを表示させ、話数をそこから取得します。

ダウンロードボタンの状態を監視して、前の作品のダウンロードが終わってボタンがクリックできるようになるまで待機します。ダウンロードする側のScriptと、ダウンロード先のフォルダに仕掛けたフォルダアクションScriptの間でハンドシェイクを行えば、ダウンロード完了するたびにファイル名を(掲載タイトルのとおりに)リネームするといった処理も実現できますが、本Scriptにはそこまで仕込んでいません。

Safariをコントロールしているため、Safariの「開発」メニューを表示させて、同メニューの「スマート検索フィールドからのJavaScriptを許可」「AppleEventからのJavaScriptを許可」の両方をチェックしておく必要があります。また、GUI Scriptingを利用しているため、「システム環境設定」の「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でScript Editorにコンピュータの制御を許可してある必要があります(書籍「AppleScript最新リファレンス」を参照してください)。

OSやSafariのバージョンが変わると動かなくなるかもしれませんが、そのあたりはGUI Scriptingの宿命ということで。また、GUI部品の情報をまるごと取得して変更を吸収する機構も組みかけていたのですが、さすがに掲載するには高度すぎたので、この初期バージョンを掲載しています。

nightuma1.png

nightuma2.png

nightuma3.png
▲この状態でAppleScriptを実行すると、ポップアップメニュー内容を変更して「ダウンロード実行します」ボタンをクリック(あとは、話数分だけ自動認識して繰り返し)

AppleScript名:小説家になろうサイトでダウンロード対象作品テキストを全話ダウンロード
– Created 2017-09-01 by Takaaki Naganoya
– 2017 Piyomaru Software
–OS Ver: macOS 10.12.6
–Safari Ver: 10.1.2

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

–ダウンロード話数をカウントする
set mCount to getMenuItems() of me

–1話から順次ダウンロードを行う
repeat with i from 1 to mCount
  –ダウンロード対象話を選択
  
selectMenuItemAtIndex(i) of me
  
delay 1
  
  
–ダウンロードボタンをクリック(イネーブルになるまで待つ)
  
clickDownloadButton() of me
  
delay 1
end repeat

–「ダウンロード実行します」ボタンをクリック
on clickDownloadButton()
  –activate application “Safari”
  
tell application “System Events”
    tell process “Safari”
      tell button 1 of UI element 1 of row 3 of table 1 of UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1
        repeat 1000 times
          set anEnabled to enabled
          
if anEnabled = true then
            click
            
return
          end if
          
delay 0.5
        end repeat
      end tell
    end tell
  end tell
end clickDownloadButton

–指定番号のメニューアイテムをクリック(選択)する
on selectMenuItemAtIndex(indNum)
  –activate application “Safari”
  
tell application “System Events”
    tell process “Safari”
      –Click “ダウンロードする部分番号” menu
      
tell pop up button 1 of group 1 of UI element 1 of row 2 of table 1 of UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1
        click
      end tell
      
      
tell menu 1 of group 1 of tab group 1 of splitter group 1 of window 1
        set mCount to count every menu item –1-based index count
        
        
tell menu item indNum
          click –Select popup menu item
        end tell
        
      end tell
      
return mCount
    end tell
  end tell
end selectMenuItemAtIndex

–指定のポップアップメニューのアイテム数をカウント
on getMenuItems()
  –activate application “Safari”
  
tell application “System Events”
    tell process “Safari”
      –Click “ダウンロードする部分番号” menu
      
tell pop up button 1 of group 1 of UI element 1 of row 2 of table 1 of UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window 1
        click –Open Menu
      end tell
      
      
tell menu 1 of group 1 of tab group 1 of splitter group 1 of window 1
        set mCount to count every menu item –Count menu items
        
tell menu item 1
          click –Close Menu
        end tell
      end tell
      
return mCount
    end tell
  end tell
end getMenuItems

★Click Here to Open This Script 

2017/09/06 郵便番号から住所を求める

アイビスのWeb APIを呼び出して(日本国内の)郵便番号から住所を求めるAppleScriptです。

とくにユーザー登録などを行わずにAPIを呼び出せます。ただし、実行結果に半角カタカナを使っているなど、とても不思議な仕様なので、適宜半角→全角変換を行う必要があるものと思われます(21世紀に入って20年近くになるのに、前世紀の遺物である「半角カタカナ」を見ることになるとは、、、)。

いろいろテスト実行してみると、夜間に停止していたり(AWSで動かしているようですが)するように見えます。

郵便番号から住所を検索するサービスといえば、XML-RPC経由での問い合わせが可能な「郵便専門ネット」が存在しています。
→ 郵便専門ネットでXML-RPC経由で郵便番号から住所を返す

さすがに「平成の大合併」といわれた、ひとときの市町村統合の動きは鎮静化していますが、郵便番号はビル単位での指定が可能なので逆に言えばビルの建て替えや統合などによる影響をつねに受け続ける状態であるともいえます。こまめに最新データにキャッチアップしてくれるサービスを選んで使いたいところです。

AppleScript名:郵便番号から住所を求める
– Created 2016-03-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://www.ibsnet.co.jp/solution/zipcloud.html
–http://piyocast.com/as/archives/4810

property |NSURL| : a reference to current application’s |NSURL|
property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property NSURLConnection : a reference to current application’s NSURLConnection

set aCode to “176-0022″
set aRec to retAddressFromZipCode(aCode) of me
–>  {kana3:”コウヤマ”, address1:”東京都”, zipcode:”1760022″, kana2:”ネリマク”, prefcode:”13″, address3:”向山”, kana1:”トウキョウト”, address2:”練馬区”}
set aStr to (address1 of aRec) & (address2 of aRec) & (address3 of aRec)
–>  ”東京都練馬区向山”

on retAddressFromZipCode(aCode)
  set reqURLStr to “http://zipcloud.ibsnet.co.jp/api/search”
  
set aRec to {zipcode:aCode}
  
set bURL to retURLwithParams(reqURLStr, aRec) of me
  
set aRes to callRestGETAPI(bURL) of me
  
set aRESTres to json of aRes
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
return results of aRESTres as record
end retAddressFromZipCode

–GET methodのREST APIを呼ぶ(パラメータをあらかじめURLに入れておくタイプ)
on callRestGETAPI(aURL)
  –Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setTimeoutInterval:60
  
set aRes to 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 NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to (dRes’s statusCode()) as integer
  
  
–Get Response Header
  
set resHeaders to (dRes’s allHeaderFields()) as record
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPI

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

★Click Here to Open This Script 

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/09/03 ソフトウェア的にアンマウントしたが物理的に接続解除していないDriveの名前を取得

Finder上でいったんマウントしたあと、アンマウントしてもMac本体に物理的に接続したままのドライブ名を取得するAppleScriptです。

  「こんなもの、誰が何のために使うんだろうか?」

と首をひねる内容ですが、自分のところにも以前に1回だけこのような質問が来たことがあります。このScriptのオリジナルはShane Stanleyによるものですが(魔改造してブラッシュアップしていますが)、どういうニーズから作ったのか本人に聞いてみたいものです。

Finder経由でドライブ情報を取得するかぎりではそういう状態にあるドライブの存在を知ることは不可能ですが、diskutil経由で取得できるというのは意外でした。そういう意味では純粋なAppleScriptで書くことも可能ですが、おそらくこの倍ぐらいの長さになるものと思われます。

一応、macOS 10.13 Beta上でテストしてありますが、RAM 4GBのMacBook Airでは何かFinderの動作が不安定でいろいろクラッシュしています。

HFSおよびAPFSのドライブには対応していますが、macOSでマウント可能な他のフォーマットのドライブすべて(HFS、HFS Plus、APFS、WebDAV、UDF、FAT、ExFAT、SMB/CIFS、AFP、NFS、FTP、Xsan、NTFS、CDDAFS、ISO9660)を試せているわけではないので、実用的なレベルに持っていくためにはそのあたりを攻める(実際にテストして拡充させる)必要があることでしょう。

AppleScript名:ソフトウェア的にアンマウントしたが物理的に接続解除していないDriveの名前を取得
– Created 2017-06-20 Shane Stanley
– Modified 2017-06-20 Takaaki Naganoya
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
–http://piyocast.com/as/archives/4802

property NSString : a reference to current application’s NSString
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableSet : a reference to current application’s NSMutableSet
property NSMutableArray : a reference to current application’s NSMutableArray

set dRes to retUnmountedAndUnremovedDriveNames() of me
–> {”Data”}

on retUnmountedAndUnremovedDriveNames()
  set theResult to do shell script “diskutil list -plist”
  
  
– make dictionary from property list
  
set theResult to NSString’s stringWithString:theResult
  
set pListDict to theResult’s propertyList()
  
  
– extract relevant info
  
set disksAndParitions to pListDict’s objectForKey:“AllDisksAndPartitions”
  
  
set partitionsArray to NSMutableArray’s array() – to store values
  
repeat with anEntry in disksAndParitions
    set thePartitions to (anEntry’s objectForKey:“Partitions”)
    
if thePartitions = missing value then – no partitions means a volume
      (partitionsArray’s addObject:anEntry)
    else
      (partitionsArray’s addObjectsFromArray:thePartitions)
    end if
  end repeat
  
  
– filter by Content type
  
set thePred to NSPredicate’s predicateWithFormat:“Content == ’Apple_HFS’ OR Content == ’Apple_APFS’ “
  
set partitionsArray to partitionsArray’s filteredArrayUsingPredicate:thePred
  
  
– get names
  
set dList to (partitionsArray’s valueForKey:“VolumeName”) as list
  
  

  
set edList to retEjectableDriveNames() of me
  
set the end of edList to retBootDriveName() of me
  
set the end of edList to missing value –消し込みのために追加
  
  
set aSet to NSMutableSet’s setWithArray:dList
  
set bSet to NSMutableSet’s setWithArray:edList –Boot drive & local ejectable drives
  
  
aSet’s minusSet:bSet –補集合
  
set dRes to aSet’s allObjects() as list
  
  
return dRes
end retUnmountedAndUnremovedDriveNames

on retEjectableDriveNames()
  tell application “Finder”
    try
      set dList to name of every disk whose ejectable is true
    on error
      set dList to {}
    end try
  end tell
  
return dList
end retEjectableDriveNames

on retBootDriveName()
  tell application “Finder”
    return name of startup disk
  end tell
end retBootDriveName

★Click Here to Open This Script 

2017/09/02 Finder上で選択中のファイルの中に書かれているタイトルに基づいてリネーム

Finder上で選択中のテキストファイルの中に書かれているタイトルを正規表現による検索で検出して、ファイル名に指定するAppleScriptです。

技術的にはぜんぜん見るべき点がありませんが、そこそこ役立つ見本です(ありものをつなぎ合わせた書き捨てレベル)。

「小説を読もう」サイトからKnight’s and Magicのテキストを(テキストエンコーディングにUTF-8を指定して)ダウンロードしてきたら、

text_download.png

ファイル名が、

filename_before.png

のようになっていたので、これを、

filename_after.png

のようにリネームしたいと考え、各テキストファイル中に書かれているタイトル部分を、

file_contents.png

正規表現で検索して指定してみました。ほとんどテキストの1行目にタイトルが書かれているので、1行目の内容をファイル名にしてもよかったのですが、

knight_magic4.png

このように(↑)たまにイレギュラーなファイルが存在しているので、正規表現でサーチしています。

実際にテストしてみたところ、全角文字の「#」と半角文字の「#」が登場していたので、全角文字で検索して存在しないようであれば半角文字で再検索するようになっています。

数百個ぐらいのファイルの処理だと、このようにFinderからselectionを拾ってきても十分なところですが、数千個以上になると別の方法を考えるべきでしょう。

AppleScript名:Finder上で選択中のファイルの中に書かれているタイトルに基づいてリネーム
– Created 2017-09-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4797

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

tell application “Finder”
  set aSel to selection as alias list
  
if aSel = {} or aSel = “” then return
end tell

repeat with i in aSel
  set aStr to (read i as «class utf8»)
  
  
–全角のタイトルマークでタイトルを検索
  
set aList to retHeaders(aStr, “#”) of me
  
if aList is not equal to {} then
    set newName to (contents of first item of aList)
    
tell application “Finder”
      set name of i to newName & “.txt”
    end tell
  else
    –半角のタイトルマークでタイトルを検索
    
set bList to retHeaders(aStr, “#”) of me
    
if bList is not equal to {} then
      set newName to (contents of first item of bList)
      
tell application “Finder”
        set name of i to newName & “.txt”
      end tell
    else
      log {i as string}
    end if
  end if
end repeat

on retHeaders(aCon, aHeaderMark)
  set tList to {}
  
set regStr to “^” & aHeaderMark & “{1,6}[^” & aHeaderMark & “]*?$”
  
  
set headerList to my findPattern:regStr inString:aCon
  
repeat with i in headerList
    set j to contents of i
    
set regStr2 to “^” & aHeaderMark & “{1,6}[^” & aHeaderMark & “]*?”
    
set headerLevel to length of first item of (my findPattern:regStr2 inString:j)
    
set the end of tList to (text (headerLevel + 1) thru -1 in j)
  end repeat
  
  
return tList
end retHeaders

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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’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/28 指定フォルダ内の指定文字列を名称に含むファイル、フォルダを抽出する v2

指定フォルダ内に存在するファイル/フォルダを名称の一部に含むキーワードで抽出するAppleScriptです。NSFileManager経由で高速に処理します。

指定フォルダ内のファイルやフォルダを求める処理は、初歩の初歩で行うものですが、macOS 10.6以降はFinder経由でこれを行うと処理速度が落ちるようになってきました。Finderのチューニングの問題だと思うのですが、AppleScriptからの問い合わせに対してあまり高速に結果を返さないようになってきました(時期的に見ると、ちょうどFinderがCocoa化されたタイミング。Finderのパフォーマンスを確保するための技術的な問題があったのでは?)。

macOS 10.6当時は「ゆくゆくはFile処理はすべてSystem Eventsに移管するのではないか?」と感じていましたが、どうもこのプランは立ち消えになったようで、Finder TabやらFinder TagがFinderのAppleScript用語辞書に実装されないまま、うやむやに。

今日、Finder経由でのファイル情報の取得はとてもコスト(=処理時間)のかかる処理になってきました(条件を何も指定しなければそれなりの速度は出ます。条件指定を行うとちょっと、、、)。逆に、ファイルの移動や削除については大幅に速度が落ちるといったことはありません。

さらに、macOS 10.13で試してみると、Finder経由で指定文字列をファイル名に含むファイルの抽出が遅くなっています(as alias listで処理結果をcastしても遅い)。数千ファイル存在するようなフォルダで実行すると、かーなり遅いです(4,300ファイルのフォルダに対して実行してみたら、SSD搭載機であってもAppleEvent Time Out(=180 sec)にひっかかるぐらい待たされる)。

# 搭載RAM 4GBのMacBook Air 2011でmacOS 10.13betaを試しているので、メモリが少なすぎてパフォーマンス低下をきたしている可能性もあります。ねんのため。

macOS 10.13上でもshell scriptやCocoa経由でのAppleScript処理は問題なく速いので、実用的な速度のAppleScriptを書こうとしたら「なるべくFinder経由でファイル処理を行わない」のがセオリーになりつつあります。

さらに、Xcode上のCocoa AppleScript appletでも各種ファイル処理(System Events経由で特定フォルダへのパスを求めるような気軽な処理)が実行できないケースもあります。指定フォルダ内のファイル/フォルダの一覧取得については、NSFileManager経由で処理することが妥当だと感じるようになりました。

処理結果についてはfileのリストで返すものとPOSIX pathで返すものを用意してみました。fileで返しておけばaliasに型変換してGUIアプリケーションに渡せますし、POSIX pathで返しておけばCocoa系のAPI経由での処理との相性がいいところです。

AppleScript名:指定フォルダ内の指定文字列を名称に含むファイル、フォルダを抽出する v2
– Created 2017-08-28 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4794

property NSURLIsDirectoryKey : a reference to current application’s NSURLIsDirectoryKey
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application’s NSDirectoryEnumerationSkipsHiddenFiles
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableArray : a reference to current application’s NSMutableArray
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to current application’s NSDirectoryEnumerationSkipsPackageDescendants
property NSFileManager : a reference to current application’s NSFileManager
property |NSURL| : a reference to current application’s |NSURL|
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants

set sourceFolder to POSIX path of (choose folder)
set aKeyword to “スクリーン”

set file1Res to my filterOutFilesByFileName:aKeyword fromDirectory:sourceFolder
–>  {file “Cherry:Users:maro:Desktop:スクリーンショット 2017-04-15 13.18.03.png”, file “Cherry:Users:maro:Desktop:スクリーンショット 2017-04-15 13.48.44.png”, …}
set file2Res to my filterOutPOSIXPathsByFileName:aKeyword fromDirectory:sourceFolder
–>  {”/Users/maro/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, “/Users/maro/Desktop/スクリーンショット 2017-04-15 13.48.44.png”, ….}

set folder1Res to my filterOutFoldersByFolderName:aKeyword fromDirectory:sourceFolder
–>  {file “Cherry:Users:maro:Desktop:スクリーン”}
set folder2Res to my filterOutDirPOSIXPathsByFolderName:aKeyword fromDirectory:sourceFolder
–>  {”/Users/maro/Desktop/スクリーン”}

–Get files

–指定フォルダ内の指定文字列を含むファイル名のファイルをfileのlistで抽出する
on filterOutFilesByFileName:fileNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = false then
      anArray’s addObject:(aValue’s |path|() as «class furl»)
    end if
  end repeat
  
  
return anArray as list
end filterOutFilesByFileName:fromDirectory:

–指定フォルダ内の指定文字列を含むファイル名のファイルをPOSIX pathのlistで抽出する
on filterOutPOSIXPathsByFileName:fileNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = false then
      anArray’s addObject:(aValue’s |path|())
    end if
  end repeat
  
  
return anArray as list
end filterOutPOSIXPathsByFileName:fromDirectory:

–Get folders

–指定フォルダ内の指定文字列を含むフォルダ名のフォルダをfileのlistで抽出する
on filterOutFoldersByFolderName:folderNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, folderNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = true then
      anArray’s addObject:(aValue’s |path|() as «class furl»)
    end if
  end repeat
  
  
return anArray as list
end filterOutFoldersByFolderName:fromDirectory:

–指定フォルダ内の指定文字列を含むフォルダ名のフォルダをPOSIX pathのlistで抽出する
on filterOutDirPOSIXPathsByFolderName:folderNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, folderNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = true then
      anArray’s addObject:(aValue’s |path|())
    end if
  end repeat
  
  
return anArray as list
end filterOutDirPOSIXPathsByFolderName:fromDirectory:

★Click Here to Open This Script 

2017/08/27 文の意味類似度の評価

Apitore「文の意味類似度の評価」APIを呼び出して、文単位で意味的な近さを評価するAppleScriptです。

ApitoreのREST API呼び出しのために、Apitoreにユーザー登録してAccess Tokenを取得し、retAccessToken()内に記述しておいてください(自分はKeychainにAccessTokenを記録して呼び出すライブラリを利用しています)。

掲載リストそのままでAccess Tokenを記入していない状態だとエラーになります。

「文の意味類似度の評価」APIはWord2Vecを用いているわけですが、Word2Vecの処理のためにWikipediaのダンプ内容を学習させており、ローカルでもWord2Vecの演算を行いたいところ。もう、いっそのことOSにWikipediaのローカルダンプを含めて配布してほしい今日このごろです(OSインストーラのサイズが倍ぐらいになりそうですが)。

AppleScript名:文の意味類似度の評価
– Created 2017-08-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4792

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSMutableData : a reference to current application’s NSMutableData
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSString : a reference to current application’s NSString
property NSURLConnection : a reference to current application’s NSURLConnection
property NSURLComponents : a reference to current application’s NSURLComponents
property NSNumber : a reference to current application’s NSNumber
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set targStr1 to "戦場の絆REV.4において、ガンダムはコスト250の格闘機である"
set targStr2 to "戦場の絆REV.2において、ガンダムはかつてコスト280の近距離機であった"
set aRes to calcSimiliarity(targStr1, targStr2) of me

set targStr3 to "この秋、戦場の絆REV.4にGブルとザクレロが宇宙ステージ専用機体として登場するらしい。"
set targStr4 to "バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011。"
set bRes to calcSimiliarity(targStr3, targStr4) of me

return {aRes, bRes}
–> {0.9, 0.0}–1.0に近いほど類似度が高い。0に近いほど類似度が低い

on calcSimiliarity(targStr1, targStr2)
  set reqURLStr to "https://api.apitore.com/api/53/sentence-similarity/eval"
  
set accessToken to retAccessToken() of me –Access Token
  
  
set aReq to {text1:targStr1, text2:targStr2}
  
set aRec to {access_token:accessToken}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
set aRESTres to (json of aRes)
  
return roundingDownNumStr(aRESTres’s similarity, 2) of me
end calcSimiliarity

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:"POST"
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:"application/json" forHTTPHeaderField:"Content-Type"
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to 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 NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return "xxXXXxxX-XxXx-XXXX-xXXX-XXxXXxxXxxXx" –API Tore Access Token
end retAccessToken

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingDownNumStr

★Click Here to Open This Script 

2017/08/22 CIFilterで画像切り抜き

CIFilterで画像の切り抜きを行うAppleScriptです。指定画像と同一階層のフォルダにUUID.pngの切り抜き画像を作成します。

Cocoaでの画像切り抜きは、

NSBitmapImageRepで切り抜き元画像の指定範囲のBitmapを取得して、新規NSBitmapImageRepに描画して結果を取得

というのが常識的なやり方のようですが、CIFilterの機能を用いて切り抜きできることを知ったので、比較のために実行してみました。

crop100images.png

結局、CIFilterの方が20倍ぐらい遅かったので、NSBitmapImageRepを使うやり方で問題ないことがわかりました。NSImageを100回指定範囲で切り抜くのに、NSBitmapImageRepで0.10950499773秒(1回あたり0.001秒)、このCIFilter経由だと1.957534968853秒(1回あたり0.02秒)でした(@MacBook Pro Retina 2012 Core i7 2.66GHz)。

もしかしたら、最近のGPU性能が向上したマシンだとこの差が小さくなるのかもしれません(CPU性能はたいして向上していない一方で、GPU性能は大きく向上しているはずなので)。

AppleScript名:CIFilterで画像切り抜き
– Created 2017-08-22 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”
–http://piyocast.com/as/archives/4788

property CIFilter : a reference to current application’s CIFilter
property CIVector : a reference to current application’s CIVector
property CIImage : a reference to current application’s CIImage
property NSString : a reference to current application’s NSString
property NSUUID : a reference to current application’s NSUUID
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSImage : a reference to current application’s NSImage
property |NSURL| : a reference to current application’s |NSURL|

–画像を選択
set aPath to POSIX path of (choose file of type {“public.image”})

set aNSImage to NSImage’s alloc()’s initWithContentsOfFile:aPath
set aSize to aNSImage’s |size|()
set aWidth to aSize’s width()
set aHeight to aSize’s height()

–Crop Image by CIFilter
set cropRes to cropNSImageWithCIFilter(aNSImage, 0, (aHeight - 100), 100, 100) of me

–Save as PNG
set fRes to retUUIDfilePath(aPath, “png”) of me
set sRes to saveNSImageAtPathAsPNG(cropRes, fRes) of me

–NSImageをCIImageに変換してCIfilterを実行
on cropNSImageWithCIFilter(aNSImage, x1, y1, xWidth, yHeight)
  set aCIImage to convNSImageToCIimage(aNSImage) of me
  
set aFilter to CIFilter’s filterWithName:“CICrop”
  
set aCIVector to CIVector’s vectorWithX:x1 Y:y1 Z:xWidth W:yHeight
  
  
aFilter’s setDefaults()
  
aFilter’s setValue:aCIImage forKey:“inputImage”
  
aFilter’s setValue:aCIVector forKey:“inputRectangle”
  
  
set filteredImage to (aFilter’s outputImage)
  
set newNSImage to convCIimageToNSImage(filteredImage) of me
  
  
return newNSImage
end cropNSImageWithCIFilter

on convCIimageToNSImage(aCIImage)
  set aRep to NSBitmapImageRep’s alloc()’s initWithCIImage:aCIImage
  
set tmpSize to aRep’s |size|()
  
set newImg to NSImage’s alloc()’s initWithSize:tmpSize
  
newImg’s addRepresentation:aRep
  
return newImg
end convCIimageToNSImage

on convNSImageToCIimage(aNSImage)
  set tiffDat to aNSImage’s TIFFRepresentation()
  
set aRep to NSBitmapImageRep’s imageRepWithData:tiffDat
  
set newImg to CIImage’s alloc()’s initWithBitmapImageRep:aRep
  
return newImg
end convNSImageToCIimage

on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

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

★Click Here to Open This Script 

2017/08/16 Markdown書類から見出し(Header)行を抽出_v2

指定のMarkdown書類から正規表現で見出し(Header)行を抽出し、見出しレベルと見出しテキストを出力するAppleScriptです。

指定フォルダ下のすべてのMarkdown書類をSpotlight処理で抽出し、各書類をこのScriptで処理。まとめて見出しレベルを統計処理してみました。複数の書籍で見出しレベルの分布などを比較するなど、割と重そうな処理をあっさり(数秒のオーダーで)処理できたのでよかったと思います。

AppleScript名:Markdown書類から見出し(Header)行を抽出_v2
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4776

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

set aFile to choose file of type {“net.daringfireball.markdown”} –Markdown書類のUTI
set aStr to (read aFile as «class utf8»)
set aList to retHeaders(aStr) of me
–>  {{3, “choose file, choose folderで選んだ対象の名称変更”}, {3, “choose folderで選んだフォルダ内のファイルの名称変更”}, {3, “Finder上で選んだ(selection)ファイルの名称変更”}, {3, “POSIX pathのファイルの名称変更”}, {3, “ファイルを移動させたうえで名称変更”}, {3, “要注意事項(超重要、生死にかかわる)”}}

on retHeaders(aCon)
  set tList to {}
  
set regStr to “^#{1,6}[^#]*?$”
  
  
set headerList to my findPattern:regStr inString:aCon
  
repeat with i in headerList
    set j to contents of i
    
set regStr2 to “^#{1,6}[^#]*?”
    
set headerLevel to length of first item of (my findPattern:regStr2 inString:j)
    
set the end of tList to {headerLevel, text (headerLevel + 1) thru -1 in j}
  end repeat
  
  
return tList
end retHeaders

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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’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/13 ASOCからclass名を抽出してpropertyとして展開

Cocoaの機能を呼び出すASOCのscript自体を、Script Editorをコントロールして書き換えるAppleScriptです。Script Editorの最前面でオープン中のAppleScript書類(front document)の内容を読み取って書き換えを行い、新規AppleScript書類に出力します。

rewriteasoc1.gif

このところ、Blogへの掲載時にASOCのプログラムを手で書き換えていました。

  「current application’s NSString」

とプログラム中に書いておくと、プログラム1行が長くなり、Blog掲載時に読みにくくなってしまうためです。これを、

  「property NSString : a reference to current application’s NSString」

とプロパティ宣言してまとめていました(Script Debuggerのテンプレート風に)。

この作業が予想外にかったるいので、Script EditorをコントロールしてAppleScriptを書き換えるAppleScriptを書いてみました(こういうのをまとめたのがPiyomaru Script Assistantであります)。

AppleScriptを記述する「Script Editor」自体、AppleScriptでコントロール可能な「スクリプタブルな」アプリケーションの1つなので、AppleScriptからScript Editorをコントロールして編集中のAppleScript書類を加工する、という処理は日常茶飯事的に行なっています。そういう処理のうちの1つです。

それほど本気で書いていないので、正規表現での文字抽出などがかなりヌルい感じ(得意な人から見ると頭を抱えるレベル)ですが、ぼちぼち書きかえていけばよいでしょう。

また、試作品レベルなのでscript文によるscript objectの分割などはいっさい考慮していません。

置換除外リストの中には「実際に動かしてみたら正規表現によるピックアップが甘くて置換ターゲットにゴミが入っていた」ことへの小手先での対処データなどが冒頭に入りつつ、Objective-CではなくCの関数(property宣言できない)およびEnumの数値が大きすぎてAppleScriptの数値表現範囲を超えてしまい代入できないものなどを入れてあります。ただし、「とりあえず動けばいい」というレベルでしか書いていないので、完全なものではありません。

Script Editorでオープン中の最前面のAppleScriptを書き換えるようになっています。一番簡単なのはASObjC Explorer 4やScript Debuggerなどで本Scriptをオープンして実行する方法ですが、Script MenuやScript Editorのコンテクストメニューから呼び出すとよいでしょう。

AppleScript名:ASOCからclass名を抽出してpropertyとして展開
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4774

set aCon to getFrontmostSEContents() of me
set aaList to paragraphs of aCon

set aList to retPropertiesClass(aCon) of me
set bList to retCurrentApplicationsClass(aCon) of me
set cList to {“\”", “\”,”, “NSMakeRange”, “NSMakePoint”, “NSMakeRect”, “NSUTF16BigEndianStringEncoding”, “NSUTF16LittleEndianStringEncoding”} –置換してはいけないリスト。text encoding系はenumの桁数が足りなくてpropertyに代入できない

set aSet to current application’s NSMutableSet’s setWithArray:aList –property宣言文の一覧
set bSet to current application’s NSMutableSet’s setWithArray:bList –script中のcurrent application文のクラス
set cSet to current application’s NSMutableSet’s setWithArray:cList

bSet’s minusSet:aSet –current applicationで指定したクラスで、property宣言していないものを計算
bSet’s minusSet:cSet –置換禁止リスト

set dList to bSet’s allObjects() as list

–Scriptの本文を書き換える
repeat with i in dList
  set j to contents of i
  
set tmpStr to “current application’s “ & j
  
set aCon to repChar(aCon, tmpStr, ” “ & j) of me
end repeat

–挿入部分のテキストを組み立てる
set repStr to return
repeat with i in dList
  set j to contents of i
  
set repStr to repStr & “property “ & j & ” : a reference to current application’s “ & j & return
end repeat
set repStr to repStr & return

set aInsPoint to getFirstInsertionPoint(aCon) of me

tell application “Script Editor”
  set aDoc to make new document
  
tell front document
    set contents to aCon
    
set selection to paragraph aInsPoint
    
set contents of selection to the repStr
    
    
try
      compile
    end try
  end tell
end tell

on getFirstInsertionPoint(aCon)
  set aList to paragraphs of aCon
  
set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j = “” then
      exit repeat
    end if
    
set aCount to aCount + 1
  end repeat
  
  
return aCount
end getFirstInsertionPoint

on retPropertiesClass(aCon)
  set resList to {}
  
set regStr to “(property .*?:)”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “property”, “”) of me
    
set j3 to repChar(j2, “:”, “”) of me
    
set j4 to repChar(j3, ” “, “”) of me
    
    
set the end of resList to j4
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retPropertiesClass

on retCurrentApplicationsClass(aCon)
  set resList to {}
  
set regStr to “(current application’s .*? )”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)    
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “’s “, “”) of me
    
set j3 to repChar(j2, “)”, “”) of me
    
set j4 to repChar(j3, “}”, “”) of me
    
set j5 to repChar(j4, “}”, “”) of me
    
set j6 to repChar(j5, ” “, “”) of me
    
    
set the end of resList to j6
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retCurrentApplicationsClass

on getFrontmostSEContents()
  tell application “Script Editor”
    set docCount to count every document
    
if docCount = 0 then error “No Document”
    
tell front document
      return contents
    end tell
  end tell
end getFrontmostSEContents

on replaceThis:thePattern inString:theString usingThis:theTemplate
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theResult to theRegEx’s stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
  
return theResult as text
end replaceThis:inString:usingThis:

on findPattern:thePattern inString:theString
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s 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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to current application’s NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

on findPattern:thePattern inString:theString capturing:n
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s 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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to current application’s NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set oneFind to (item i of theFinds)
    
if (oneFind’s numberOfRanges()) as integer < (n + 1) then
      set end of theResult to missing value
    else
      set theRange to (oneFind’s rangeAtIndex:n)
      
set end of theResult to (theNSString’s substringWithRange:theRange) as string
    end if
  end repeat
  
return theResult
end findPattern:inString:capturing:

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

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

★Click Here to Open This Script 

AppleScript名:ASOCからclass名を抽出してpropertyとして展開(書き換え後)
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4774

property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSString : a reference to current application’s NSString
property NSArray : a reference to current application’s NSArray
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSMutableSet : a reference to current application’s NSMutableSet
property NSRegularExpression : a reference to current application’s NSRegularExpression

set aCon to getFrontmostSEContents() of me
set aaList to paragraphs of aCon

set aList to retPropertiesClass(aCon) of me
set bList to retCurrentApplicationsClass(aCon) of me
set cList to {“\”", “\”,”, “NSMakeRange”, “NSMakePoint”, “NSMakeRect”, “NSUTF16BigEndianStringEncoding”, “NSUTF16LittleEndianStringEncoding”} –置換してはいけないリスト。text encoding系はenumの桁数が足りなくてpropertyに代入できない

set aSet to NSMutableSet’s setWithArray:aList –property宣言文の一覧
set bSet to NSMutableSet’s setWithArray:bList –script中のcurrent application文のクラス
set cSet to NSMutableSet’s setWithArray:cList

bSet’s minusSet:aSet –current applicationで指定したクラスで、property宣言していないものを計算
bSet’s minusSet:cSet –置換禁止リスト

set dList to bSet’s allObjects() as list

–Scriptの本文を書き換える
repeat with i in dList
  set j to contents of i
  
set tmpStr to “current application’s “ & j
  
set aCon to repChar(aCon, tmpStr, ” “ & j) of me
end repeat

–挿入部分のテキストを組み立てる
set repStr to return
repeat with i in dList
  set j to contents of i
  
set repStr to repStr & “property “ & j & ” : a reference to current application’s “ & j & return
end repeat
set repStr to repStr & return

set aInsPoint to getFirstInsertionPoint(aCon) of me

tell application “Script Editor”
  set aDoc to make new document
  
tell front document
    set contents to aCon
    
set selection to paragraph aInsPoint
    
set contents of selection to the repStr
    
    
try
      compile
    end try
  end tell
end tell

on getFirstInsertionPoint(aCon)
  set aList to paragraphs of aCon
  
set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j = “” then
      exit repeat
    end if
    
set aCount to aCount + 1
  end repeat
  
  
return aCount
end getFirstInsertionPoint

on retPropertiesClass(aCon)
  set resList to {}
  
set regStr to “(property .*?:)”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “property”, “”) of me
    
set j3 to repChar(j2, “:”, “”) of me
    
set j4 to repChar(j3, ” “, “”) of me
    
    
set the end of resList to j4
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retPropertiesClass

on retCurrentApplicationsClass(aCon)
  set resList to {}
  
set regStr to “(current application’s .*? )”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)    
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “’s “, “”) of me
    
set j3 to repChar(j2, “)”, “”) of me
    
set j4 to repChar(j3, “}”, “”) of me
    
set j5 to repChar(j4, “}”, “”) of me
    
set j6 to repChar(j5, ” “, “”) of me
    
    
set the end of resList to j6
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retCurrentApplicationsClass

on getFrontmostSEContents()
  tell application “Script Editor”
    set docCount to count every document
    
if docCount = 0 then error “No Document”
    
tell front document
      return contents
    end tell
  end tell
end getFrontmostSEContents

on replaceThis:thePattern inString:theString usingThis:theTemplate
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theResult to theRegEx’s stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
  
return theResult as text
end replaceThis:inString:usingThis:

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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

on findPattern:thePattern inString:theString capturing:n
  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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set oneFind to (item i of theFinds)
    
if (oneFind’s numberOfRanges()) as integer < (n + 1) then
      set end of theResult to missing value
    else
      set theRange to (oneFind’s rangeAtIndex:n)
      
set end of theResult to (theNSString’s substringWithRange:theRange) as string
    end if
  end repeat
  
return theResult
end findPattern:inString:capturing:

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

–1D/2D Listをユニーク化
on uniquify1DList(theList as list, aBool as boolean)
  set aArray to NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
return bArray as list
end uniquify1DList

★Click Here to Open This Script 

2017/08/11 日本語形態素解析【新語対応】_ipadic_neologd(POST版)

apitoreのREST API「日本語形態素解析【Neologd対応】」のPOST対応版APIを呼び出すAppleScriptです。

apitore上の既存の形態素解析APIがPOST対応し、1MBまでのサイズのテキストの形態素解析が行えるようになったので、ためしに呼んでみることにしました。

ただし、テキストのサイズが大きくなった場合の処理時間が読めないので、いきなりMAXの1MBのテキストを形態素解析させるのは得策ではないでしょう(自分も様子見中)。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得して、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

個人的にはREST APIのAccess TokenをmacOSのKeychainに入れて、アカウント名とサイト名でKeychainに問い合わせる「keychain Lib」AppleScript Librariesを用いています。

AppleScript名:日本語形態素解析【新語対応】_ipadic_neologd(POST版)
– Created 2017-08-11 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
–use keychainLib : script “keychainLib”
–http://piyocast.com/as/archives/4773

property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableData : a reference to current application’s NSMutableData
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLConnection : a reference to current application’s NSURLConnection
property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents

(*)
tell current application
  set aTargStr to read (choose file) as «class utf8»–Read text as UTF-8
end tell
*)

–2017/7/3 Ver: POST対応。1MBまでのテキストを一気に形態素解析できるようになった
set aTargStr to “「ACE COMBAT INFINITY」3周年記念キャンペーンを実施。記念エンブレムをプレゼント”
set aTargList to paragraphs of aTargStr

set reqURLStr to “https://api.apitore.com/api/7/kuromoji-ipadic/tokenize”
set accessToken to retAccessToken() of me —Access Token
set aReq to {texts:aTargList}
set aRec to {access_token:accessToken}
set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
set aRESCode to responseCode of aRes
set aRESHeader to responseHeader of aRes
set aRESTres to (json of aRes)
return aRESTres as record

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to 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 NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return “XXXXxxxX-xxxx-XXXx-xxxX-XxxxXxXxxXXx” –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/08/10 Jedit ΩのScript Menuの各種制限

Artman 21のマルチスタイルテキストエディタ「Jedit Ω」をためしてみたところ、Jedit Ω内蔵のScript Menuにはいろいろ制約があることが見えてきました。

AppleScriptは、実行環境が異なると一部の挙動が変わってくるため(Finderの選択ファイルの取得とか、セキュリティ面での制約とか)、さまざまな実行環境の傾向やクセをつかんでおくことは重要です。

Jedit Ω内蔵のScript Menu

さまざまな制約が存在しています。まず、現在のmacOSで標準的なバンドル形式のAppleScriptを実行できません。Script中にさまざまなリソースやフレームワークを入れて呼び出すようなことはできません。
→ Jedit Ωが、単純に拡張子「.scpt」のScriptのみ認識してバンドル形式の「.scptd」を認識しないために発生する問題のようです

Jedit ΩのScript Menu経由でオープン中の書類のクローズはできるものの、クローズ後もファイルのロックが継続された状態なのか、クローズ後に他のアプリケーションやScriptの機能で書き換えようとしてもできませんでした。実用上かなり制約が大きいため、このJedit Ωメニュー経由だと(現状では)何もできない感じです。

AppleScriptObjCのAppleScript(use framework “Foundation”を含む)の実行自体は行えるようです。ただし、前述のとおりバンドル形式では認識されません。このため、Script自体にFrameworkを同梱したり、AppleScript Librariesを同梱しても(この環境では)実行できません。

OSグローバルのScript Menu

メニューバーに常時表示され、最前面のアクティブなアプリケーションに合わせて該当するScriptが表示される仕組みです。

Jedit Ωに対してこのScript MenuからAppleScriptを実行して、オープン中のRTFをクローズして書き換え、変更して再オープンしてみたところ、とくに問題なく実行できました。また、当然バンドル形式のScriptの実行も問題ありません。

Script Editor

この環境ではとくに制約はありません。

2017/08/04 LocalのHTMLからMeta Keywordsとtitleを返す

LocalにダウンロードしたHTMLから検索用のMeta Keyとtitleを取得するAppleScriptです。

実行にはオープンソースの「HTMLReader.framework」(By Nolan Waite)を必要とします。

たまにWebサイトからcurlコマンドなどでHTMLをダウンロードして整理しておくことがあります。その際に、HTMLのファイル名が連番でとくに内容がよくわからないような場合に、ファイル名にHTMLのtitleを入れておき、HTML書類のコメントにHTML検索用キーワードを(HTMLから取得して)入れておくと整理しやすいことでしょう。

そのような用途のために作成したものです。実際には、1ファイルの情報を取得するものではなく、指定フォルダ以下のHTMLをすべて取得して順次ファイル名の変更や検索用タグづけを行うような処理を行います。

実行のためにはHTMLReader.frameworkをXcode上でビルドして、~/Library/Frameworksフォルダに入れて実行する必要があります。

ローカルにダウンロードしたHTMLということで、実ファイルを調べればテキストエンコーディングはわかるので決め打ちで指定していますが、別にAppleScriptだけでテキストエンコーディングの自動判別できるので、そのような機能を呼び出すようにしてもよいでしょう。

AppleScript名:LocalのHTMLからMeta Keywordsとtitleを返す
– Created 2017-08-04 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
–http://piyocast.com/as/archives/4760

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

set aPOSIX to POSIX path of (choose file)
set aRec to retTitleAndMetaKeywordsFromLocalHTML(aPOSIX, NSJapaneseEUCStringEncoding) of me
–> {”title string”, “keywords strings”}

on retTitleAndMetaKeywordsFromLocalHTML(aPOSIX, anEncoding)
  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
  
  
–HTML Title
  
set aTitleRes to ((aHTML’s nodesMatchingSelector:“title”)’s textContent’s firstObject()) as string
  
  
–Headers
  
set aHeaderRes to (aHTML’s nodesMatchingSelector:“head”)’s firstObject()
  
set headerElements to aHeaderRes’s children()’s array()
  
repeat with i in headerElements as list
    set j to contents of i
    
set aClass to j’s |class|()
    
set aKeys to “”
    
if aClass = current application’s HTMLElement then
      set aRec to j’s attributes()
      
set aName to (aRec’s valueForKey:“name”) as string
      
set aKeys to (aRec’s valueForKey:“content”) as string
      
if aName = “keywords” then
        exit repeat
      end if
    end if
  end repeat
  
  
return {aTitleRes, aKeys}
end retTitleAndMetaKeywordsFromLocalHTML

★Click Here to Open This Script 

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/08/01 JTHistogramでArrayのヒストグラムを計算

オープンソースの「JTHistogram」(By Kinokoo)をフレームワーク化したJTHistogramKitを呼び出して、NSArrayのヒストグラムを計算するAppleScriptです。

テスト用のランダム配列データの作成には、オープンソースの「objc-classes-dc-randomize」(By masakihirokawa)をフレームワーク化した「ArrayRandomize」を利用しています。

JTHistogramには該当要素数をかぞえる|histogram|()というメソッドと、構成比率を計算して求めるrelativeHistogram()というメソッドがあり、場合に応じて使い分けるとよいでしょう。

大量のデータのヒストグラム処理は割と見かけるものなので、このように高速に処理できる道具を揃えておくことには意義があります。

■乱数リスト作成+ヒストグラム(度数分布)計算 所要時間(@MacBook Pro Retina 2012 Core i7 2.66GHz, 8GB RAM)

 千項目(1,000 items):0.002 sec
 1万項目(10,000 items):0.019 sec
 10万項目(100,000 items):0.25 sec
 100万項目(1,000,000 items):3.6 sec
 1000万項目(10,000,000 items):41 sec

NSArrayの状態であれば(listに変換していない状態ならば)RAM 8GBのMacBook Proでも1000万項目の配列を扱えることに驚かされます。ASの常識的な10万項目程度なら0.25秒と非常に高速にヒストグラム計算が行えます。

ただし、得られたデータをそのままrecordに変換できないケースもあるので(ラベル値がAppleScriptのrecordで許容されないものだったりで)、値を取り出すにはCocoaのmethodを用いる必要があります。

1〜10万件程度の、AppleScriptにはやや手のかかる規模のデータの度数分布計算を行なっても、高速に処理できています(そりゃ、Objective-Cのプログラムを呼び出しているだけなので)。

JTHistogram.frameworkおよびArrayRandomize.frameworkをmacOS 10.10以降用にビルドしたバイナリを用意しましたので、自己責任で~/Library/Frameworksフォルダに入れて使って使ってみてください。

→ JTHistogramKit framework Binary

→ ArrayRandomize Framework Binary

AppleScript名:JTHistogramでArrayのヒストグラムを計算
– Created 2017-08-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “JTHistogramKit” –https://github.com/Kinokoo/JTHistogram
–http://piyocast.com/as/archives/4757

set anArray to current application’s NSMutableArray’s arrayWithArray:{1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3}
set histObj to current application’s JTHistogram’s alloc()’s initWithArray:anArray

set histDict to histObj’s |histogram|()
–>  (NSDictionary) {0:0, 3:4, 2:2, 1:6}

set relativeHistogramDict to histObj’s relativeHistogram()
–>  (NSDictionary) {0:0, 3:66.66667, 2:33.33334, 1:100}

★Click Here to Open This Script 

AppleScript名:JTHistogramでArrayのヒストグラムを計算(100,000項目)
– Created 2017-08-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ArrayRandomize” –https://github.com/masakihirokawa/objc-classes-dc-randomize
use framework “JTHistogramKit” –https://github.com/Kinokoo/JTHistogram
–http://piyocast.com/as/archives/4757

set anArray to (current application’s DCRandomize’s shuffle:1 max:999999)
set histObj to current application’s JTHistogram’s alloc()’s initWithArray:anArray

set histDict to histObj’s |histogram|()
–>  (NSDictionary) {0:0, 67423:1, 61298:1, 55173:1, 49048:1, 42923:1,….

★Click Here to Open This Script 

2017/07/30 ASOCで画像の破損チェック

Cocoaの機能を用いて画像の破損チェックを行うAppleScriptです。

これまでにも、OS環境の移り変わりとともに破損画像のチェックを行うサブルーチンを整備してきました。

→ 2014/04/30 破損画像チェック 10.9
→ 2009/03/31 破損画像チェック
→ 2008/04/17 JPEG画像の破損チェック

ただ、昔のものの方がチェック能力が高く、Cocoaの機能を利用したものは破損検出レベルはそれほど高くはありません(並列処理には向いている?)。Image Eventsを利用するものも破損検出的には同程度の機能なので、おしなべてこの程度の検出しかできていないわけですが・・・。

  Type 1:オープンできない
  Type 2:画像書き出し時にエラーになる
  Type 3:ファイルオープン時にアラートが出る

本ルーチンで検出できるのはType 1のオープンできない画像のみです。

サブルーチン中で画像破損のチェックを2回(画像のNSImageへの読み込み、NSImageの内容の妥当性チェック)行なっていますが、それらの呼称と上記のType 1〜3は名前が似ていいるだけで同一のものではありません。

AppleScript名:ASOCで画像の破損チェック
– Created 2016-08-24 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4752

set aPath to (choose file of type {“public.image”})
set aRes to confirmImage(aPath) of me

–画像の破損チェック(can not open画像はチェックOK) 破損時にはfalseを返す
on confirmImage(aPath)
  set aType to type identifier of (info for aPath)
  
set aPOSIX to POSIX path of aPath
  
  
set aImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:aPOSIX
  
if aImage = missing value then return {false, aType, 1}
  
  
set aRes to aImage’s isValid()
  
return {aRes, aType, 2}
end confirmImage

★Click Here to Open This Script 

2017/07/25 Bluetoothに接続中のデバイス名を取得するv4

Macに接続中のBluetoothデバイス名を取得するAppleScriptです。

一応、実行前にMac本体のBluetoothがオンになっているかどうかチェックしてから取得しています。

ペアリング中のBluetoothデバイス(主にAirPods)を強制的にペアリング解除できないかと調べていたら途中でできたものです。

ただし、macOS 10.13 Betaではこの通りには取得できていなかったのと、一部のデバイス(Bluetooth接続のゲームコントローラー)についてはこのやり方では「接続中のデバイス」の絞り込みができませんでした。もっと接続中であることを識別する適切な方法があるのかも?

AppleScript名:Bluetoothに接続中のデバイス名を取得するv4
– Created 2017-07-24 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “IOBluetooth”
–http://piyocast.com/as/archives/4747

set pRes to getBluetoothPowerState() of me
if pRes = false then return

set dArray to current application’s IOBluetoothDevice’s pairedDevices()
set aRes to my filterRecListByLabel1(dArray, “mIONotification != 0″)
set dNames to (aRes’s mName) as list
–>  {”Piyomaru AirPods”, “Takaaki Naganoya のマウス”}

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel1(aRecList, aPredicate as string)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
return filteredArray
end filterRecListByLabel1

–Mac本体のBluetoothのパワー状態を取得
on getBluetoothPowerState()
  set aCon to current application’s IOBluetoothHostController’s alloc()’s init()
  
set pRes to (aCon’s powerState()) as boolean
end getBluetoothPowerState

★Click Here to Open This Script 

2017/07/23 defaultsAppLibを呼び出してURLスキームに対応するアプリケーション情報を取得、設定

オープンソースのコマンドラインアプリケーション「defaultapp」(By Grayson Hansard)を呼び出して、各URLスキームに対応するアプリケーションを取得したり、設定するAppleScriptライブラリです。

実際に試すためには、事前にライブラリをダウンロードして~/Library/Script Librariesフォルダに入れておいてください。

→ defaultapplib.zip

このライブラリには最低限の機能しか実装していません。「URLスキームに対応するアプリケーションを登録」(setDefaultAppForScheme)と、「URLスキームに対応するアプリケーションのパスを確認」(getDefaultAppForScheme)です。

以下のリストは、AppleScript Librarirs「defaultsAppLib」がインストールされている環境で、同ライブラリを呼び出すものです。

AppleScript名:defaultsAppLibを呼び出してURLスキームに対応するアプリケーション情報を取得、設定
use AppleScript version “2.4″
use scripting additions
use defautApLib : script “defaultAppLib”
–https://github.com/Grayson/defaultapp
–http://piyocast.com/as/archives/4742

set aRes to getDefaultAppForScheme(“http”) of defautApLib
–> “/Applications/Safari.app”

set bRes to getDefaultAppForScheme(“ftp”) of defautApLib
–> “/Applications/Transmit.app”

set cRes to setDefaultAppForScheme(“ftp”, “Safari”) of defautApLib
–> true

set bRes to getDefaultAppForScheme(“ftp”) of defautApLib
–> “/Applications/Safari.app”

★Click Here to Open This Script 

2017/07/21 指定のPNG画像をASCII ART化してTextEditでオープン v2

jp2aを利用して指定のPNG画像を色付きHTML形式のアスキーアート化して出力し、HTMLをRTFに変換してTextEditでオープンしてフォントを変更するAppleScriptです。

指定のJPEG画像をアスキーアート化するjp2aは、Homebrew経由でインストールしてください。

asciiart4_resized.png

asciiart3_resized.png
▲このように縦長で余白の多い画像から、自動で余白部分をトリミングして処理

asciiart_z.png
▲左側はPNG画像の余白トリミング処理を行ったもの、右側はトリミング処理を行わなかったもの

アスキーアート化するにあたって、元画像の余白部分をトリミングできたほうが良好な結果が得られるため、処理対象をJPEG画像からPNG画像(背景透過)に変更し、以前に使った画像余白トリミングフレームワーク「KGPixelBoundsClipKit」を用いてトリミングしてJPEG画像に変換。

トリミングしたJPEG画像をjp2aでHTMLのアスキーアートに変換し、さらにHTMLをスタイル付きテキスト(NSAttributedString)に変換。

スタイル付きテキストをRTF(リッチテキストフォーマット)に変換してファイル出力。RTFになればTextEditで扱えるので、TextEditでオープンして本文のフォントを等幅フォント(Osaka-mono)に指定してウィンドウのサイズを変更しています。

テストする際には、KGPixelBoundsClipKitフレームワークのバイナリを~/Library/Frameworksフォルダに入れてお試しください。

–> Download Framework Binary

AppleScript名:指定のPNG画像をASCII ART化してTextEditでオープン v2
– Created 2017-07-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “KGPixelBoundsClipKit” –https://github.com/kgn/KGPixelBoundsClip
–http://piyocast.com/as/archives/4736

set aFile to choose file of type {“public.png”}

–PNG画像の余白をトリミング
set anImage to (current application’s NSImage’s alloc()’s initWithContentsOfFile:(POSIX path of aFile))
set bImage to anImage’s imageClippedToPixelBounds()

–トリミング結果をデスクトップにJPEG形式で保存
set aDesktopPath to (current application’s NSProcessInfo’s processInfo()’s environment()’s objectForKey:(“HOME”))’s stringByAppendingString:“/Desktop/”
set savePath to aDesktopPath’s stringByAppendingString:((current application’s NSUUID’s UUID()’s UUIDString())’s stringByAppendingString:“.jpg”)
set fRes to saveNSImageAtPathAsJPG(bImage, savePath, 100) of me

–ASCII ARTに変換してHTMLとして出力
set anAlias to (POSIX file (savePath as string)) as alias
set htmlRes to jpegToAsciiArt(anAlias, 120) of me

–出力されたHTMLデータを評価してスタイル付きテキストに変換
set htmlData to current application’s NSString’s stringWithString:htmlRes
set keyList to {current application’s NSDocumentTypeDocumentAttribute, current application’s NSCharacterEncodingDocumentAttribute}
set valList to {current application’s NSHTMLTextDocumentType, current application’s NSUTF8StringEncoding}
set optDict to current application’s NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
set aStyledStr to current application’s NSAttributedString’s alloc()’s initWithData:(htmlData’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)) options:optDict documentAttributes:(missing value) |error|:(missing value)

–スタイル付きテキストをRTFとしてデスクトップに保存
set targFol to POSIX path of (path to desktop)
set aUUID to current application’s NSUUID’s UUID()’s UUIDString() as text
set bRes to my saveStyledTextAsRTF(aUUID, targFol, aStyledStr) –PDFで書き出す
set newPath to targFol & aUUID & “.rtf”

–保存したRTFをTextEditでオープンして等幅フォント設定して、Window横幅を変更
tell application “TextEdit”
  activate
  
open (POSIX file newPath) as alias
  
  
tell text of document 1
    set font of every attribute run to “Osaka-Mono”
  end tell
  
  
tell window 1
    set {x1, y1, x2, y2} to bounds
    
set bounds to {x1, y1, (x1 + 800), y2}
  end tell
end tell

–NSImageを指定パスにJPEG形式で保存
on saveNSImageAtPathAsJPG(anImage, outPath, qulityNum as real)
  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 NSJPEGFileType) |properties|:{NSImageCompressionFactor:qulityNum})
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsJPG

–与えられたファイルパスのJPEG画像をASCII ARTに変換して返す
on jpegToAsciiArt(anImageAlias, digitNum as integer)
  set sText to “/usr/local/bin/jp2a -f –html-raw –colors –width=” & (digitNum as string) & ” -i “ & quoted form of POSIX path of anImageAlias
  
set tRes to do shell script sText
  
return tRes
end jpegToAsciiArt

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName, targFol, aStyledString)
  –Convert NSMutableStyledStrings to RTF
  
set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to current application’s NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(current application’s NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
– build path based on title
  
set theName to current application’s NSString’s stringWithString:aFileName
  
set theName to theName’s stringByReplacingOccurrencesOfString:“/” withString:“_”
  
set theName to theName’s stringByReplacingOccurrencesOfString:“:” withString:“_”
  
set thePath to current application’s NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

★Click Here to Open This Script 

2017/07/09 指定URLにリンクされているPDFをすべて指定のフォルダにダウンロードする

指定URLのHTMLにリンクされているPDFをすべて指定のフォルダにダウンロードするAppleScriptです。

従来、この手の処理はSafariに対してdo javascript命令を実行していましたが、呼び出しにそこそこ時間がかかります。

そこで、SafariまかせにせずにAppleScript側でオープンソースのフレームワーク「HTMLReader」を呼び出してHTMLを解析したところ、圧倒的に高速になりました。

同じページ内のリンク箇所の抽出処理だと、

  Safari+do javascript:89 seconds
  HTMLReader.framework:0.064 seconds (First Run Time:0.831 seconds)

と、Safariに対してdo javascriptコマンドを実行しないAppleScriptのほうが100〜1,400倍高速に処理できています。本Script全体の処理時間については、PDFのダウンロード処理をともなうためネットワークの速さに依存しますが、1分かからない程度で終わることでしょう。Safari+do javascriptだとリンク先の抽出がまだ終わっていないぐらいの時間です。

実行にあたっては、HTMLReaderのプロジェクトをダウンロードしてXcode上でビルドし、出来上がったフレームワークのバイナリを~/Library/Frameworksにインストールしておく必要があります。

ただ、PDFのURLが厳密にわかっている連番のファイルならshellのcurlコマンドを呼び出してダウンロードさせれば1行で終わってしまう内容ではあります。

AppleScript名:指定URLにリンクされているPDFをすべて指定のフォルダにダウンロードする
– Created 2017-07-09 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
–http://piyocast.com/as/archives/4720

set aTargFol to POSIX path of (choose folder with prompt “PDFダウンロード先のフォルダを選択”)
set aTargPath to current application’s NSString’s stringWithString:aTargFol

set aStr to “http://yakumo-tajimi.com/dl.html” –Safariの最前面のウィンドウからとってきてもよい
set aList to getWebLinkURLs(aStr, “pdf”) of me

repeat with i in aList
  set j to contents of i
  
set jURL to (current application’s |NSURL|’s URLWithString:j)
  
set {exRes, headerRes, aData} to checkURLResourceExistence(jURL, 10) of me
  
  
if exRes = true then
    set cURL to (current application’s |NSURL|’s URLWithString:j)
    
set cFileName to (cURL’s |lastPathComponent|()) as string
    
set savePath to (aTargPath’s stringByAppendingPathComponent:cFileName)
    
set wRes to (aData’s writeToFile:savePath atomically:true)
  end if
end repeat

–指定のURLのページのHTMLソースからリンクを抽出して、指定拡張子に合うものだけをフルパスのURL化して返す
on getWebLinkURLs(anURLstr, linkFileExt)
  –URLの妥当性チェック(存在チェック)
  
set aURL to (current application’s |NSURL|’s URLWithString:anURLstr)
  
set {exRes, headerRes, aData} to checkURLResourceExistence(aURL, 3) of me
  
if exRes = false then error “Illegal URL Error” –エラー発生時に処理打ち切り
  
  
–HTMLのソースを取得する
  
set conType to headerRes’s valueForKeyPath:“Content-Type”
  
set aHTML to current application’s HTMLDocument’s documentWithData:aData contentTypeHeader:conType
  
  
–リンク箇所を抽出
  
set aTextArray to ((aHTML’s nodesMatchingSelector:“a”)’s textContent) as list –リンク文字
  
set aLinkArray to ((aHTML’s nodesMatchingSelector:“a”)’s attributes’s valueForKeyPath:“href”) as list –URL
  
  
  
–取得したリンクを拡張子で絞り込みつつ、それぞれフルパスのURLを組み立てる
  
set urlList to {}
  
set aaURL to aURL’s URLByDeletingLastPathComponent()
  
  
repeat with i in aLinkArray
    set bURL to (current application’s |NSURL|’s URLWithString:i)
    
set aRes to (bURL’s |scheme|()) as string
    
set aExt to (bURL’s |pathExtension|()) as string
    
    
if aRes = “missing value” and aExt = linkFileExt then
      –想定URL(指定サイト内)のファイルへのリンクの処理
      
set aaaURL to (aaURL’s URLByAppendingPathComponent:i)
      
set aaaURLstr to (aaaURL’s absoluteString()) as string
      
set the end of urlList to aaaURLstr
    else if aRes is not “missing value” and aExt = linkFileExt then
      –指定外のURL(想定サイト外のファイルへのリンクなど)の処理
      
set the end of urlList to (aaURL’s absoluteString()) as string
    end if
  end repeat
  
  
–重複部分を除去してユニークなリストにして返す
  
return uniquify1DList(urlList, true) of me
end getWebLinkURLs

– 指定URLにファイル(画像など)が存在するかチェック
–> {存在確認結果(boolean), レスポンスヘッダー(NSDictionary), データ(NSData)}
on checkURLResourceExistence(aURL, timeOutSec as real)
  set aRequest to (current application’s NSURLRequest’s requestWithURL:aURL cachePolicy:(current application’s NSURLRequestUseProtocolCachePolicy) timeoutInterval:timeOutSec)
  
set aRes to (current application’s 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 -1 –error
  end if
  
return {(aResCode = 200), hRes, dRes}
end checkURLResourceExistence

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

★Click Here to Open This Script 

2017/07/07 Keynoteの現ページと次ページで場所が被っていても始点座標が異なるオブジェクトの始点座標をそろえる

編集中のKeynote書類で現在のスライドと次のスライド上の指定した種類のオブジェクトの座標情報をスキャンして、「重なっているが原点座標が異なる」ものを検出して原点座標を同じにそろえるAppleScriptです。

2017-07-07-13_06_20.gif

Keynoteで資料を使っていて、(複数ページ間で同じ場所に存在すべき)オブジェクトの位置が微妙にズレていることが気になることがあります。

ただ、いちいち座標情報をひろって手で修正するのは面倒なので、AppleScriptで自動修正させてみました。

重なっているオブジェクト同士であっても、始点座標が同じものは無視します(修正する必要はないので)。

Script実行時にチェック対象の2ページのうち、最初のページを表示していることを想定しています。

オブジェクトの領域の重なり判定は2つだけを想定しています。それ以上の個数のオブジェクトの重なりは無視しています。

KeynoteのAppleScript用語辞書がselection(選択中のオブジェクト)を取得できないという「残念な点」があるので、そこが重ね重ねも残念です。

AppleScript名:Keynoteの現ページと次ページで場所が被っていても始点座標が異なるオブジェクトの始点座標をそろえる
– Created 2017-07-07 by Takaaki Naganoya
– 2017 Piyomaru Software
– v1:オーバーラップしているオブジェクトの個数が2個のみの状況を想定
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4718

tell application “Keynote”
  tell front document
    set sNum to slide number of (current slide)
  end tell
end tell

–表示中のページ(slide)と、その次のページ(slide)で、
–指定オブジェクト(shape)の座標情報をリストアップ
set {rect1, obj1} to getObjInfoList(sNum, “shape”) of me –現在のページ(slide)
set {rect2, obj2} to getObjInfoList(sNum + 1, “shape”) of me –現在+1ページ

set hitRect to {} –衝突していつつもイコールではないオブジェクトの座標情報を入れる
set hitObj to {} –衝突しているオブジェクト(最初のページ、次のページ)をペアで入れる

set p1Count to 1

–表示中のページ(slide)と、その次のページ(slide)で、
–重なっていつつも始点座標が異なるオブジェクト(shape)を総当たりでリストアップ
tell application “Keynote”
  tell front document
    repeat with i in rect1
      set j to contents of i
      
      
set p2Count to 1
      
      
repeat with ii in rect2
        set jj to contents of ii
        
        
–指定オブジェクト同士の領域が重なっているかどうかチェック
        
set aRes to detectRectanglesCollision(j, jj) of me
        
–始点座標がイコールかどうかチェック(イコールのものは修正不要)
        
set origRes to (origin of j) = (origin of jj)
        
        
if (aRes = true) and (origRes = false) then
          set the end of hitRect to j
          
set the end of hitObj to {contents of item p1Count of obj1, contents of item p2Count of obj2}
        end if
        
        
set p2Count to p2Count + 1
        
      end repeat
      
      
set p1Count to p1Count + 1
    end repeat
  end tell
end tell

–current slideを2ページ目に変更
set cRes to changeCurrentSlide(sNum + 1) of me

–オーバーラップしていつつも始点座標が異なるアイテムの個数を数える
set tCount to count every item of hitRect

tell application “Keynote”
  tell front document
    tell current slide
      repeat with i from 1 to tCount
        set curPos to contents of item i of hitRect
        
set curOrig to origin of curPos
        
set curOrigX to x of curOrig
        
set curOrigY to y of curOrig
        
        
set curObj to contents of second item of item i of hitObj
        
set position of curObj to {curOrigX, curOrigY}
      end repeat
    end tell
  end tell
end tell

–最前面のKeynote書類の指定スライド(page)上の指定オブジェクトの情報(座標情報、オブジェクト情報)を返す
on getObjInfoList(aPage, objClassStr as string)
  set rectList to {}
  
set objList to {}
  
  
set cRes to changeCurrentSlide(aPage) of me
  
if cRes = false then error “Slide Range Error”
  
  
tell application “Keynote”
    tell front document
      tell current slide
        set aList to every iWork item
        
        
repeat with i in aList
          set aClass to (class of i) as string
          
          
if aClass = objClassStr then
            set aWidth to width of i
            
set aHeight to height of i
            
set {aPosX, aPosY} to position of i
            
set aRect to {origin:{x:aPosX, y:aPosY}, |size|:{|width|:aWidth, |height|:aHeight}}
            
            
set the end of rectList to aRect
            
set the end of objList to i
          end if
        end repeat
      end tell
    end tell
  end tell
  
  
return {rectList, objList}
end getObjInfoList

–NSRect同士の衝突判定
on detectRectanglesCollision(aRect, bRect)
  set a1Res to current application’s NSIntersectionRect(aRect, bRect)
  
return not (a1Res = {origin:{x:0.0, y:0.0}, |size|:{width:0.0, height:0.0}})
end detectRectanglesCollision

–表示中のスライド(ページ)を変更する
on changeCurrentSlide(targPageNum as integer)
  try
    tell application “Keynote”
      tell front document
        set sCount to count every slide
        
if targPageNum < 1 then return false –under check
        
if sCount < targPageNum then return false –over check
        
set current slide to slide targPageNum
        
return true
      end tell
    end tell
  on error
    return false
  end try
end changeCurrentSlide

★Click Here to Open This Script