macOS 10.15は毎日新たな(よくない方の)発見があって驚かされます。そんなmacOS 10.15上のPhotos.appでインポートした写真のファイル名に含まれる英小文字がすべて英大文字に変更されるというバグが報告されています。
macOS 10.14.6上のPhotos.app経由で同じ写真をインポートすると、iCloud経由でシンクロされてmacOS 10.15.1上のPhotos.appに配信されてもファイル名はオリジナルのままです。
macOS 10.15は毎日新たな(よくない方の)発見があって驚かされます。そんなmacOS 10.15上のPhotos.appでインポートした写真のファイル名に含まれる英小文字がすべて英大文字に変更されるというバグが報告されています。
macOS 10.14.6上のPhotos.app経由で同じ写真をインポートすると、iCloud経由でシンクロされてmacOS 10.15.1上のPhotos.appに配信されてもファイル名はオリジナルのままです。
iTunesLibrary.framework経由でiTunes/Music.appのライブラリのジャンルごとの再生回数を集計するAppleScriptです。
7,000曲程度入っているライブラリで、ジャンル名の名寄せも含め、開発環境(MacBook Pro Retina 2012, Core i7 2.66GHz@macOS 10.14.6)で0.7秒程度で終了します。2,500曲程度入っているMac mini 2014 Core i5 2.6GHz@macOS 10.15.1で0.6秒程度です。
macOS 10.14まで(iTunes.app)と、macOS 10.15以降(Music.app)でも同様にiTunesLibrary.framework経由でライブラリへのアクセスが行えます。
AppleScript名:iTunes Libraryの再生回数をジャンルごとに集計、ジャンル名名寄せ付き |
— Created 2019-11-10 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "iTunesLibrary" –https://developer.apple.com/reference/ituneslibrary/itlibmediaitem?language=objc property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property NSScanner : a reference to current application’s NSScanner property NSPredicate : a reference to current application’s NSPredicate property NSDictionary : a reference to current application’s NSDictionary property NSCountedSet : a reference to current application’s NSCountedSet property NSMutableArray : a reference to current application’s NSMutableArray property NSSortDescriptor : a reference to current application’s NSSortDescriptor property NSMutableCharacterSet : a reference to current application’s NSMutableCharacterSet –ジャンル名寄せリスト(2要素から構成される2D List) set genreNayoseList to {{"World", "ワールド"}, {"Anime", "アニメ"}, {"Electronic", "エレクトロニック"}, {"R&B/ソウル", "R&B/ソウル"}, {"Kayokyoku", "歌謡曲"}, {"Electronic", "エレクトロニック"}, {"Vocal", "ヴォーカル"}, {"Classical", "クラシック"}, {"Dance", "ダンス"}, {"Soundtrack", "サウンドトラック"}, {"Rock", "ロック"}} set library to current application’s ITLibrary’s libraryWithAPIVersion:"1.0" |error|:(missing value) if library is equal to missing value then return set aRes1 to (library’s applicationVersion()) as string –> "12.10.1.37" @ macOS 10.15 set aRes2 to (library’s apiMinorVersion()) –> 1 set aRes3 to (library’s apiMajorVersion()) –> 1 set playLists to library’s allPlaylists() set gArray to library’s allMediaItems()’s genre set aRes to countItemsByItsAppearance(gArray) of me set bRes to genreNayoseAndUnify(aRes, genreNayoseList) of me –> {{genreName:"サウンドトラック", numberOfTimes:1965}, {numberOfTimes:1209, genreName:"Podcast"}, {genreName:"ロック", numberOfTimes:1128}, {genreName:"クラシック", numberOfTimes:705}, {numberOfTimes:517, genreName:"ポップ"}, {genreName:"アニメ", numberOfTimes:533}, {numberOfTimes:383, genreName:"J-Pop"}, {numberOfTimes:292, genreName:"Pop"}, {numberOfTimes:279, genreName:"社会/文化"}, {numberOfTimes:252, genreName:missing value}, {genreName:"ワールド", numberOfTimes:246}, {numberOfTimes:187, genreName:"ジャズ"}, {genreName:"エレクトロニック", numberOfTimes:168}, {numberOfTimes:125, genreName:"R&B"}, {numberOfTimes:104, genreName:"ニューエイジ"}, {numberOfTimes:81, genreName:"Unclassifiable"}, {genreName:"歌謡曲", numberOfTimes:60}, {numberOfTimes:57, genreName:"Children’s"}, {numberOfTimes:54, genreName:"オルタナティブ"}, {numberOfTimes:38, genreName:"Holiday"}, {numberOfTimes:32, genreName:"Data"}, {numberOfTimes:31, genreName:"イージーリスニング"}, {genreName:"ヴォーカル", numberOfTimes:31}, {numberOfTimes:19, genreName:"iTunes U"}, {numberOfTimes:17, genreName:"フォーク"}, {numberOfTimes:15, genreName:"ブルース"}, {numberOfTimes:15, genreName:"ディズニー"}, {numberOfTimes:15, genreName:"シンガーソングライター"}, {numberOfTimes:14, genreName:"Easy Listening"}, {numberOfTimes:14, genreName:"ラテン"}, {numberOfTimes:14, genreName:"Electronica/Dance"}, {numberOfTimes:14, genreName:"個人ジャーナル"}, {genreName:"ダンス", numberOfTimes:12}, {numberOfTimes:10, genreName:"アクション/アドベンチャー"}, {numberOfTimes:9, genreName:"J-POP"}, {numberOfTimes:9, genreName:"New Age"}, {numberOfTimes:7, genreName:"演歌"}, {numberOfTimes:6, genreName:"少年"}, {numberOfTimes:6, genreName:"青年"}, {numberOfTimes:6, genreName:"キッズ/ファミリー"}, {numberOfTimes:5, genreName:"Video"}, {numberOfTimes:5, genreName:"プログラミング"}, {numberOfTimes:4, genreName:"ホリデー"}, {numberOfTimes:4, genreName:"カントリー"}, {numberOfTimes:4, genreName:"科学/医学"}, {numberOfTimes:3, genreName:"ビジネス"}, {numberOfTimes:3, genreName:"コメディ"}, {numberOfTimes:3, genreName:"Game Music"}, {numberOfTimes:3, genreName:"Latin"}, {genreName:"R&B/ソウル", numberOfTimes:5}, {numberOfTimes:2, genreName:"#NIPPONSEI @ IRC.MIRCX.COM"}, {numberOfTimes:2, genreName:"Technology"}, {numberOfTimes:2, genreName:"ヒップホップ/ ラップ"}, {numberOfTimes:2, genreName:"ヒップホップ/ラップ"}, {numberOfTimes:2, genreName:"日本"}, {numberOfTimes:2, genreName:"ドラマ"}, {numberOfTimes:1, genreName:"社会科学"}, {numberOfTimes:1, genreName:"コンピュータ/テクノロジー"}, {numberOfTimes:1, genreName:"Tech ニュース"}, {numberOfTimes:1, genreName:"科学/自然"}, {numberOfTimes:1, genreName:"その他"}, {numberOfTimes:1, genreName:"児童書フィクション"}, {numberOfTimes:1, genreName:"レゲエ"}, {numberOfTimes:1, genreName:"Lifestyle & Home"}, {numberOfTimes:1, genreName:"ホリデーミュージック"}, {numberOfTimes:1, genreName:"マネジメント/リーダーシップ"}, {numberOfTimes:1, genreName:"インストゥルメンタル"}, {numberOfTimes:1, genreName:"SF/ファンタジー"}, {numberOfTimes:1, genreName:"146"}, {numberOfTimes:1, genreName:"健康/フィットネス"}, {numberOfTimes:1, genreName:"148"}, {numberOfTimes:1, genreName:"NHK FM(東京)"}, {numberOfTimes:1, genreName:"Seattle Pacific University – Latin"}, {numberOfTimes:1, genreName:"チルドレン・ミュージック"}, {numberOfTimes:1, genreName:"名作"}, {numberOfTimes:1, genreName:"Folk"}} –ジャンルのリストを出現回数で集計 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:{"genreName", "numberOfTimes"}) end repeat –出現回数(numberOfTimes)で降順ソート set theDesc to NSSortDescriptor’s sortDescriptorWithKey:"numberOfTimes" ascending:false bArray’s sortUsingDescriptors:{theDesc} return bArray as list end countItemsByItsAppearance on genreNayoseAndUnify(aList as list, genreNayoseList as list) set gList to FlattenList(genreNayoseList) of me set didProc to {} set a2List to {} repeat with i in aList set aGenre to genreName of i if (aGenre is in gList) and (aGenre is not in didProc) then repeat with ii in genreNayoseList set jj to contents of ii if aGenre is in jj then copy jj to {g1, g2} if chkAlphabet(g1) of me = true then set targG to g2 set targG2 to g1 else set targG to g1 set targG2 to g2 end if set s1Res to searchByGenreName(aList, targG) of me set s2Res to searchByGenreName(aList, targG2) of me set s3Res to addMutipleLists({s1Res, s2Res}) of me set tmpClass to class of s3Res if tmpClass = list then set s3Res to contents of first item of s3Res end if set outRec to {genreName:targG, numberOfTimes:s3Res} set the end of didProc to g1 set the end of didProc to g2 exit repeat end if end repeat set the end of a2List to outRec else if (aGenre is not in didProc) then set the end of a2List to contents of i end if end if end repeat return a2List end genreNayoseAndUnify on searchByGenreName(aList as list, aGenreName as string) set predicatesStr to "genreName == ’" & aGenreName & "’" set anArray to (NSArray’s arrayWithArray:aList) set aPred to (NSPredicate’s predicateWithFormat:predicatesStr) set bRes to (anArray’s filteredArrayUsingPredicate:aPred) if (bRes as list) = {} then return {} set bbRes to first item of bRes return (numberOfTimes of bbRes) as list end searchByGenreName on addMutipleLists(s1List) script spdAdd property s1List : {} property s3List : {} end script copy s1List to (s1List of spdAdd) –init set s1Len to length of first item of (s1List of spdAdd) set (s3List of spdAdd) to makeZero1DList(s1Len, 0) of me repeat with i in (s1List of spdAdd) set tmpLen to length of i if tmpLen is not equal to s1Len then return false repeat with ii from 1 to s1Len set tmp1 to contents of item ii of (s3List of spdAdd) set tmp2 to contents of item ii of i set item ii of (s3List of spdAdd) to (tmp1 + tmp2) end repeat end repeat return (s3List of spdAdd) end addMutipleLists –指定要素を指定回数追加したリストを作成する on makeZero1DList(itemMax, itemElem) set allData to {} repeat itemMax times set the end of allData to itemElem end repeat return allData end makeZero1DList –By Paul Berkowitz –2009年1月27日 2:24:08:JST –Re: Flattening Nested Lists on FlattenList(aList) set oldDelims to AppleScript’s text item delimiters set AppleScript’s text item delimiters to {"????"} set aString to aList as text set aList to text items of aString set AppleScript’s text item delimiters to oldDelims return aList end FlattenList — アルファベットのみか調べて返す on chkAlphabet(checkString) set aStr to NSString’s stringWithString:checkString set allCharSet to NSMutableCharacterSet’s alloc()’s init() allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "a", 26)) allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "A", 26)) set aBool to my chkCompareString:aStr baseString:allCharSet return aBool as boolean end chkAlphabet on chkCompareString:checkString baseString:baseString set aScanner to NSScanner’s localizedScannerWithString:checkString aScanner’s setCharactersToBeSkipped:(missing value) aScanner’s scanCharactersFromSet:baseString intoString:(missing value) return (aScanner’s isAtEnd()) as boolean end chkCompareString:baseString: |
1D List(1次元配列)のデータを指定セグメント単位で合成(加算)するAppleScriptです。
何を言っているのか作った本人にしか通じない雰囲気が漂っていますが、単にデータの集計単位を変更するためのものです。
24時間を1時間単位で集計したデータを3時間単位で区切って集計するとか、午前午後(2セグメント)でまとめて集計するとかといった用途に使います。
いちじるしく、日常的に書き捨てしているレベルの処理ではありますが、再利用できる部品にまとめておきました。
AppleScript名:1Dリスト内の項目を指定セグメント単位で合成 |
— – Created by: Takaaki Naganoya – Created on: 2019/12/02 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — set aList to {0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 20, 10, 5, 3, 2, 1} set a2List to gatherEachItemsBy(aList, 3) of me –> {0, 0, 3, 12, 21, 33, 35, 6} set a3List to gatherEachItemsBy(aList, 2) of me –> {0, 0, 0, 1, 5, 9, 13, 18, 23, 30, 8, 3} set a4List to gatherEachItemsBy(aList, 4) of me –> {0, 1, 14, 31, 53, 11} set a4List to gatherEachItemsBy(aList, 5) of me –> false set a5List to gatherEachItemsBy(aList, 6) of me –> {0, 15, 54, 41} set a6List to gatherEachItemsBy(aList, 7) of me –> false set a7List to gatherEachItemsBy(aList, 8) of me –> {1, 45, 64} set a8List to gatherEachItemsBy(aList, 12) of me –> {15, 95} set a9List to gatherEachItemsBy(aList, 24) of me –> {110} set a10List to gatherEachItemsBy(aList, 0) of me –> {110} on gatherEachItemsBy(aList as list, aStep as number) set bList to {} set aLen to length of aList if aStep = 0 then set aStep to aLen if aLen mod aStep is not equal to 0 then return false repeat with i from 1 to aLen by aStep set tmpV to 0 repeat with ii from 0 to (aStep – 1) set anItem to item (i + ii) of aList set tmpV to tmpV + anItem end repeat set the end of bList to tmpV end repeat return bList end gatherEachItemsBy |
指定のRTFファイルから書式情報を取得し、フォント名とフォントサイズのペアをスタイルを反映させたポップアップメニューで選択。RTFの内容を解析したのち、ダイアログが表示されます(ここの所要時間は読ませるRTFのサイズ次第)。ポップアップメニューで書式を選ぶと、すぐさま抽出した箇所をすべてまとめて表示します。
–> Download Script Bundle with Sample data
ダイアログ表示前に書式ごとの抽出を完了し、個別のTabViewに抽出後の文字データを展開しておくので、ポップアップメニューから選択すると、展開ずみのデータを入れてあるTabViewに表示を切り替えるだけ。切り替え時に計算は行わないため、すぐに抽出ずみの文字データが表示されるという寸法です。
今回は短いサンプルデータを処理してみましたが、サンプル抽出時にあらかじめ範囲指定するなどして、データ処理規模を限定することで処理時間を稼ぐこともできることでしょう。
テキストエディットでオープン中のRTF書類のパスを取得して、それを処理対象にしてもいいでしょう。
ただし、やっつけで作ったのでスクリプトエディタ上でCommand-Control-Rで実行しないと動きません(メインスレッド実行をプログラムで強制しても、途中でクラッシュしてしまいます)。それほど時間もかけずに作ったやっつけプログラムなので、あまり原因追求も行えずそのままです。
AppleScript名:アラートダイアログ+Tab View v3.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/09/16 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" use rtfLib : script "readStyledTextLib" use parseLib : script "parseAttrLib" property NSFont : a reference to current application’s NSFont property NSView : a reference to current application’s NSView property NSAlert : a reference to current application’s NSAlert property NSColor : a reference to current application’s NSColor property NSMenu : a reference to current application’s NSMenu property NSArray : a reference to current application’s NSArray property NSTabView : a reference to current application’s NSTabView property NSPredicate : a reference to current application’s NSPredicate property NSTextView : a reference to current application’s NSTextView property NSMenuItem : a reference to current application’s NSMenuItem property NSTabViewItem : a reference to current application’s NSTabViewItem property NSPopUpButton : a reference to current application’s NSPopUpButton property NSMutableDictionary : a reference to current application’s NSMutableDictionary property NSRunningApplication : a reference to current application’s NSRunningApplication property NSFontAttributeName : a reference to current application’s NSFontAttributeName property NSKernAttributeName : a reference to current application’s NSKernAttributeName property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString property NSStrokeWidthAttributeName : a reference to current application’s NSStrokeWidthAttributeName property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName property TabPosition : 4 —0=Top, 1=Left, 2=Bottom, 3=Right, 4=None property returnCode : 0 property aTabV : missing value property selectedNum : {} set aFile to choose file of type {"public.rtf"} set aStyledStr to readRTFfile(aFile) of rtfLib set paramObj to {myMessage:"Select Style", mySubMessage:"Select style you want to filter", viewWidth:800, viewHeight:400, myStyledStr:aStyledStr} my dispTabViewWithAlertdialog:paramObj –for debug –my performSelectorOnMainThread:"dispTabViewWithAlertdialog:" withObject:paramObj waitUntilDone:true return selectedNum on dispTabViewWithAlertdialog:paramObj –Receive Parameters set aMainMes to (myMessage of paramObj) as string –Main Message set aSubMes to (mySubMessage of paramObj) as string –Sub Message set aWidth to (viewWidth of paramObj) as integer –TextView width set aHeight to (viewHeight of paramObj) as integer –TextView height set aStyledStr to (myStyledStr of paramObj) set attrResTmp to getAttributeRunsFromAttrString(aStyledStr) of parseLib set attrList to attributes of attrResTmp set menuStyle to menuList of attrResTmp set selectedNum to {} set aView to NSView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight)) –Ppopup Buttonをつくる set a1Button to NSPopUpButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, aHeight – 20, 400, 20)) pullsDown:false a1Button’s removeAllItems() –WYSIWHG popup menuをつくる set a1Menu to NSMenu’s alloc()’s init() repeat with i from 1 to (length of menuStyle) set {tmpFont, tmpSize} to contents of item i of menuStyle set aTitle to (tmpFont & " " & tmpSize as string) & " point" set aMenuItem to (NSMenuItem’s alloc()’s initWithTitle:aTitle action:"actionHandler:" keyEquivalent:"") set attributedTitle to makeRTFfromParameters(aTitle, tmpFont, tmpSize, NSColor’s blackColor()) of me (aMenuItem’s setEnabled:true) (aMenuItem’s setTarget:me) (aMenuItem’s setTag:(i as string)) (aMenuItem’s setAttributedTitle:(attributedTitle)) (a1Menu’s addItem:aMenuItem) end repeat –Ppopup Buttonにmenuをつける a1Button’s setMenu:a1Menu –Make Tab View set aTabV to NSTabView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight – 30)) aTabV’s setTabViewType:(TabPosition) –TabViewに中身を入れる repeat with i from 1 to (length of menuStyle) set {tmpFont, tmpSize} to contents of item i of menuStyle set tmpKey to (tmpFont as string) & "/" & (tmpSize as string) set tmpArry to filterRecListByLabel1(attrList, "styleKey2 == ’" & tmpKey & "’") of me set tmpStr to (tmpArry’s valueForKeyPath:"stringVal") as list set aTVItem to (NSTabViewItem’s alloc()’s initWithIdentifier:(i as string)) (aTVItem’s setLabel:(i as string)) set aTextView to (NSTextView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth – 10, aHeight – 10))) (aTextView’s setRichText:true) set tmpAttr to makeRTFfromParameters(tmpStr as string, tmpFont, tmpSize, NSColor’s blackColor()) of me (aTextView’s textStorage()’s appendAttributedString:tmpAttr) (aTVItem’s setView:aTextView) (aTabV’s addTabViewItem:aTVItem) end repeat aView’s setSubviews:{a1Button, aTabV} aView’s setNeedsDisplay:true — set up alert set theAlert to NSAlert’s alloc()’s init() tell theAlert –for Messages its setMessageText:(aMainMes) its setInformativeText:(aSubMes) –for Buttons its addButtonWithTitle:"OK" its addButtonWithTitle:"Cancel" –Add Accessory View its setAccessoryView:(aView) –for Help Button its setShowsHelp:(true) its setDelegate:(me) end tell — show alert in modal loop NSRunningApplication’s currentApplication()’s activateWithOptions:0 my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true if (my returnCode as number) = 1001 then error number -128 set selectedNum to contents of item ((a1Button’s indexOfSelectedItem()) + 1) of menuStyle end dispTabViewWithAlertdialog: on doModal:aParam set (my returnCode) to aParam’s runModal() end doModal: on alertShowHelp:aNotification display dialog "Help Me!" buttons {"OK"} default button 1 with icon 1 return false –trueを返すと親ウィンドウ(アラートダイアログ)がクローズする end alertShowHelp: –Popup Action Handler on actionHandler:sender set aTag to tag of sender as integer aTabV’s selectTabViewItemAtIndex:(aTag – 1) end actionHandler: –書式つきテキストを組み立てる on makeRTFfromParameters(aStr as string, aFontName as string, aFontSize as real, aColor) –フォント set aVal1 to NSFont’s fontWithName:(aFontName) |size|:aFontSize set aKey1 to (NSFontAttributeName) –色 set aVal2 to aColor set aKey2 to (NSForegroundColorAttributeName) –カーニング set aVal3 to 0.0 set akey3 to (NSKernAttributeName) –アンダーライン set aVal4 to 0 set akey4 to (NSUnderlineStyleAttributeName) –リガチャ –set aVal5 to 2 –全てのリガチャを有効にする –set akey5 to ( NSLigatureAttributeName) –枠線(アウトライン) –set aVal6 to outlineNum –set akey6 to ( NSStrokeWidthAttributeName) set keyList to {aKey1, aKey2, akey3, akey4} set valList to {aVal1, aVal2, aVal3, aVal4} set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary return attrStr end makeRTFfromParameters –リストに入れたレコードを、指定の属性ラベルの値で抽出 on filterRecListByLabel1(aRecList as list, aPredicate as string) set aArray to NSArray’s arrayWithArray:aRecList set aPredicate to NSPredicate’s predicateWithFormat:aPredicate set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate set bList to filteredArray return bList end filterRecListByLabel1 |
macOSには、GUIをもたないAppleScript専用の補助アプリケーションが標準装備されていますが、macOS 10.15になって正常に(期待通りに)動作しない状態でリリースされているので注意が必要というお話です。
macOSには、「●●Scripting」といったClassic Mac OSから引き継いだツール群と、「●●Events」といったMac OS X移行時に搭載されたAppleScriptからの呼び出し専用のツール群が、/System/Library/CoreServicesフォルダに入っています。
10.4 | 10.5 | 10.6 | 10.7 | 10.8 | 10.9 | 10.10 | 10.11 | 10.12 | 10.13 | 10.14 | 10.15 | 11.0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ColorSyncScripting | ⬛︎ | ⬛︎ | |||||||||||
Database Events | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ |
Digital Hub Scripting | ⬛︎ | ⬛︎ | |||||||||||
FontSync Scripting | ⬛︎ | ⬛︎ | |||||||||||
Image Events | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ★ | ★ |
Keychain Scripting | ⬛︎ | ⬛︎ | ⬛︎ | ||||||||||
System Events | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ | ⬛︎ |
URL Access Scripting | ⬛︎ | ⬛︎ | ⬛︎ |
▲「最新事情がわかるAppleScript 10大最新技術 Ver.2.0」より抜粋(36ページ掲載)、最新OSに合わせて改定
最新版のmacOS 10.15でもSystem Events、Image Events、Database Eventsの3つが残っており、System Eventsはよく使われています。
Image Eventsはごくまれに、Database Eventsに至ってはほぼ実戦で利用したことがありませんが、存在していることは確認しています。
そんなImage Eventsが、macOS 10.15上でデフォルト設定ではユーザー指定の画像ファイルにアクセスできなくなっていることが確認されています。
システム環境設定の「セキュリティとプライバシー」>「セキュリティ」>「フルディスクアクセス」にImage Eventsを登録すればアクセスできるようにはなりますが、「セキュリティ強化」の美名のもとに機能の自家中毒を起こしている状態といえるでしょう。
一方、Database Eventsではそんなことはなく、Database Events経由で作成したデータベースに問題なくアクセスできますし、Database Eventsの唯一にして最大の機能である「AppleScriptのフィルタ参照でデータベースのしぼりこみ検索を行う」機能も健在です(それしかできないので)。
AppleScriptによる画像処理は、ICCプロファイルを重視してAdobe Photoshopで行うか、RGB画像をスピーディーにCocoa Frameworkで処理するかの方向に二分されており、Image Eventsで行うような基礎的な画像処理はCocoa Frameworkで肩代わりされる方向にあります。Image Eventsは入門者向けのなんちゃって画像処理レベルで用いられるものであり、個人的にはほとんど利用していません。事情を知らない入門者がハマる落とし穴が新しく1つ作られたといったところでしょうか。
▲openコマンドの引数はfile(実際にはalias)と書かれているが、furlで渡さないとオープンされないImage Events
▲SQLiteラッパーで、素朴すぎて機能がまったく不明だったDatabase Events。まさかフィルタ参照で絞り込みを行う専用ツールだったとは(ソート命令も検索命令もない謎仕様)
macOS標準搭載のテキストエディタ「テキストエディット」の最前面のドキュメントから書式情報を取得し、フォント名とフォントサイズのペアをスタイルを反映させたポップアップメニューで選択。選択したスタイルが該当する箇所を抽出したのちにテキスト化してTextViewで結果を表示します。
–> Download Code-signed AppleScript applet executable with libraries
▲Sample RTF(なんでもOK)
▲添付のAppleScriptアプレットは、初回実行時に制御許可のダイアログが表示されます。OKすれば、2回目以降はダイアログ表示されません
▲書式情報をRTF書類から読み込んで、スタイルを反映させたポップアップメニューで表示。サンプル文をRTF書類から取得しているものの、改行部分だけがヒットしたりとなかなかうまくありません
▲一覧から選択
▲RTF書類から指定書式に相当するテキストを抽出して出力
AppleScript名:指定のフォントとサイズに該当するテキストを抽出する v2.scptd |
— Created 2019-11-29 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use wgMenuLib : script "wysiwygMenuLib" –WYSIWYGポップアップメニューダイアログ ライブラリ use dtLib : script "dTextView" –TextView表示ライブラリ script spd property fontList : {} property fontSizes : {} property textList : {} property outList : {} property combiList : {} end script set (outList of spd) to {} set (fontList of spd) to {} set (fontSizes of spd) to {} set (textList of spd) to {} set (combiList of spd) to {} –TextEdit書類から書式情報を根こそぎ取得 tell application "TextEdit" tell text of front document set (fontList of spd) to font of every attribute run –フォント名 set (fontSizes of spd) to size of every attribute run –文字サイズ set (textList of spd) to character of every attribute run –文字 end tell end tell –ポップアップメニュー選択用のデータを作成 set aLen to length of (fontList of spd) set menuList to {} set sampleList to {} repeat with i from 1 to aLen set tmpFont to contents of item i of (fontList of spd) set tmpSize to contents of item i of (fontSizes of spd) set tmpSample to (contents of item i of (textList of spd)) as string set tmpList to {tmpFont, tmpSize} if {tmpList} is not in (combiList of spd) then set the end of (combiList of spd) to tmpList set the end of menuList to (tmpFont & " — " & tmpSize as string) & " point ex.:" & tmpSample end if end repeat –WYSIWYGポップアップメニューで項目選択 set paramObj to {myMessage:"Select Attribute", mySubMessage:"Select target attribute", segmentMes:{menuList}, segmentTitles:{"Select combination of font name and size"}, segmentAttributes:(combiList of spd)} set fRes to makeMenuAndcheck(paramObj) of wgMenuLib if fRes = 0 then return copy item (first item of fRes) of (combiList of spd) to {aFont, aSize} –取得した条件にもとづいて、根こそぎ取得した書式と照合を行いつつループ repeat with i from 1 to length of (fontList of spd) set tmpFont to contents of item i of (fontList of spd) set tmpSize to contents of item i of (fontSizes of spd) –文字サイズをざっくり判定することで「大、中、小」といったおおまかな切り分けも可能 if {tmpFont, tmpSize} = {aFont, aSize} then set tmpCon to (contents of item i of (textList of spd)) as string if tmpCon does not contain "•" then –ごみ取り set the end of (outList of spd) to tmpCon end if end if end repeat –結果をTextViewで表示 set aStr to retListedText(outList of spd, return) of me set eRes to (display text view aStr main message "Result" sub message "" with properties {font name:aFont, size:aSize, width:800, height:400}) on retListedText(aList, aSeparator) set aText to "" set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to aSeparator set aText to aList as text set AppleScript’s text item delimiters to curDelim return aText end retListedText |
Mac App Storeで販売したはいいものの、途中(macOS 10.13)でド派手なバグをOS(PDFView)に作られ、AppleのDevelopper Supportに正式に連絡してもなしのつぶてでそのままMac App Storeに置かれている「Double PDF」。さすがに現状のままだとまずすぎるので、macOS 10.14/10.15上で動作するように改修作業を行なっている今日このごろ(macOS 10.13.xでは、動くことは動くんですがOS自体の未修正バグが多すぎて動作保証いたしかねます)。
# OSをアップデートすると機能が落ちるとは何事だろう?
PDFViewのド派手なバグ(currentPageを取得できない)を回避するために、自前でAppleScriptでいろいろ回避コードを走らせているので、ページめくりのスピードが落ちています(個人的にこれのために作ったので、テンションが落ちるところです)。
初版では画像処理にGPUImage.frameworkを同梱して利用していましたが、GPUImage1だとOpenGL経由でGPUの機能にアクセスしており、OpenGL自体が最新のmacOS 10.15では非推奨となっており、Mac App Storeでリジェクトされる危険性があります(より新しい代替APIであるMetalを使わないと因縁を付けられる気が、、、)。
そこで、GPUImageに依存しているコードをすべて書き換え、GPUImageを完全に取り外しました。これには、よくよく調べたら、PDFの各ページをグレースケール画像に変換する箇所でしか利用していなかったことが大きいです。これで、名実ともに「すべてAppleScriptで記述したアプリケーション」に。
GPUImageの除去は使い勝手には一切影響しません。若干の前向きな機能追加も行なっておきましょう。
これまでに意見として寄せられていないものの、どうもグレースケール画像で比較を行うことに不満を持っていたユーザーが一定数いるものとにらんでいたので、カラーのまま比較する機能を追加しました。
正直なところ、「カラー比較モード」は個人的には不要と思っている(文字主体の書籍の校正用に作った)のですが、念のためです。
Double PDFはデータ上のささいな違いを「見逃す」ように作ってあります。これは、Adobe AcrobatのPDF比較機能が、見た目には影響を及ぼさないデータ上の些細な違いばかりピックアップして実用性がまったくないことから思いついたものです。見た目に影響のない差異を見逃して、見た目や文字で差が発生している箇所を指摘するツールという味付けになっています。
画像サイズを小さくした上でグレースケール化して比較するのはそのための重要な機能です。ただ、間違って色が変わったことを検出したいような用途もあることでしょう。
あとは、PDFからテキスト抽出をしたときに、PDF書き出し時の環境(OSバージョン)によってはNo Width Spaceが検出されるものがあるため、No Width Spaceの削除機能を追加しました。
細かい箇所でまだ不具合点(右側のビューワーのノンブルが表示されないことがある)がありますが、とりあえず出してみてもよさそうな気配はしています。
地味なところで画像素材をDark Mode対応させました。Credits.rtfのダークモード対応など、地味に工数が増えるのは勘弁してほしいです。あとは、外部アプリケーション操作要求を行うためのInfo.plistのエントリ追加などもあります。これを追加しないと外部アプリケーションへの操作要求のダイアログ自体が出ない=一切操作できないので要注意点です。
ほかにも、現行のXcodeでCocoa AppleScriptアプリケーションのプロジェクトを作ると、デフォルトではソースコード開示状態のビルドセッティングになってしまうので、これも要チェック点でしょう。まさかと思ってこの設定を再確認してみたら、execute onlyになっていませんでした。怖すぎ、、、
指定のNSImageをグレースケール画像にしたのちNSImageで出力するAppleScriptです。
GPUImageをプログラムから取り外すにあたって必要になったので用意しました。ふだん、画像変換系のScriptはNSImageで入力したのちにファイルに出力するように組んであったのですが、入出力ともにNSImageにしてあるプログラムが手元に存在していなかったので。
GPUImageが自分にとって使いやすかったのは、入出力にNSImageが使えたからだと思います。CGImageだとAppleScriptからアクセスできないですし、CIImageもアクセスできないわけではないですが、ちょっと遠回りになります。
AppleScript名:指定のNSImageをグレースケールに |
— Created 2019-07-09 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSColorSpace : a reference to current application’s NSColorSpace property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep property NSColorRenderingIntentPerceptual : a reference to current application’s NSColorRenderingIntentPerceptual set aFile to POSIX path of (choose file of type {"public.image"} with prompt "Select Image A") set aImage to NSImage’s alloc()’s initWithContentsOfFile:aFile set gImage to convNSIMageAsGray(aImage) of me –NSImageをグレースケールに変換してNSImageで返す on convNSIMageAsGray(anImage) set imageRep to anImage’s TIFFRepresentation() set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep set bRawimg to convBitMapToDeviceGrey(aRawimg) of me set bImage to NSImage’s alloc()’s initWithSize:(bRawimg’s |size|()) bImage’s addRepresentation:bRawimg return bImage end convNSIMageAsGray –NSBitmapImageRepをグレースケールに変換する on convBitMapToDeviceGrey(aBitmap) set aSpace to NSColorSpace’s deviceGrayColorSpace() set bRawimg to aBitmap’s bitmapImageRepByConvertingToColorSpace:aSpace renderingIntent:(NSColorRenderingIntentPerceptual) return bRawimg end convBitMapToDeviceGrey |
与えられた文字列がURLエンコードずみ文字列かどうかをチェックするAppleScriptです。
なんでそんなものが必要になったかといえば、macOS 10.15のPDFView上で発生した(”applescript://” schemeの)URL EventがデコードされずにOSデフォルト指定のスクリプトエディタに転送される、URLデコード不良バグが発生。これに対処した簡易PDFビューワーも用意しましたが、macOS 10.15.2 Beta3でこのバグが修正されているように見えます。
# 一度発生したバグは、今後もAppleが再発しないよう継続して監視する必要があります
すると、今度は簡易PDFビューワー側でURLイベントをデコードしてからScript Editorに転送している処理が「余計」になってしまいます。再デコードした文字列は途中で途切れてしまうので、処理対象の文字列がURLエンコードされたものかどうかをチェックする必要が出てきました。
このままOSにURLデコード不良バグが残存するよりは、こうしたプログラムでチェックしてから処理するほうがよいでしょう。
AppleScript名:URLエンコードずみ文字列チェック.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/29 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSString : a reference to current application’s NSString set aStr to "applescript://com.apple.scripteditor?action=new&script=display%20dialog%20%22TEST%22" set aRes to chkEncodedURLorNot(aStr) of me –> true –URL Encoded set bStr to "display dialog \"TEST\"" set bRes to chkEncodedURLorNot(bStr) of me –> false –Not URL encoded on chkEncodedURLorNot(aStr) set nsStr to NSString’s stringWithString:aStr set decodedStr to nsStr’s stringByRemovingPercentEncoding() if (nsStr’s isEqualToString:decodedStr) then return false –encoded URL else return true –Not encoded URL end if end chkEncodedURLorNot |
AppleがPDFViewのScripting Bridge関連データにバグを作り、レポートしても一向に直さないため、Mac App Storeに出しているものの、macOS 10.13以降のOSでは正常動作しない、Piyomaru Software謹製のPDF差分ブラウザ「Double PDF」。
Appleが作ったバグのせいで基礎的な動作が阻害されてしまっているので、機能アップは一切考えないで(機能アップ版として別のものを作っていたので)、Double PDFについては現在のOSで動くレベルで改修をすすめています(作業量が見えない、、、、)。
–> Demo Movie of Current Version of Double PDF v2.0 (on macOS 10.14.6)
AppleScriptの箱庭世界でモノを作っている分には環境変化は割と穏やかですが、直接Cocoaの機能に手を出しはじめると、OSバージョンごとの無意味な名称改変とか、予告なしの仕様変更とか、このような未改修のバグに直面することがあり、Mac OS X 10.3で「is in」という基礎的かつ重要な演算子にバグを作られた時と同質の負の感情を喚起されるものがあります。
正式にDevelopper Supportの窓口から詳細に症状を伝えてレポートしても無視されているので、AppleのDevelopper Supportもバグの報告でパンクしているということでしょうか。macOS 10.13以降、バグレポートしても返事も来ないのが日常化してしまったので(AppleScript関連ではないバグがほとんどなのですが)、バグを直す気がないんでしょうか。
▲macOS 10.14.6上で改修中。まだ画面操作まわりに着手しただけ
▲macOS 10.15.1上で動作確認。最近のmacOSはBetaで動作確認してもRelease版の品質がBeta以下なので、Beta上で確認する意義が、、、
Appleによって破壊された機能は、PDFViewまわりの基礎的な機能。
- (IBAction)goToFirstPage:(nullable id)sender; - (IBAction)goToLastPage:(nullable id)sender; - (IBAction)goToNextPage:(nullable id)sender; - (IBAction)goToPreviousPage:(nullable id)sender; @property (nonatomic, readonly, nullable) PDFPage *currentPage;
などです(他にもあるかも。いや、確実にある)。つまり、PDFビューワー機能を作ろうとすると、いろいろ苦労させられる状態です。
苦労が必要ということは、余計に処理時間が必要ということで、処理速度はオリジナル版よりも遅くなっています。とくに、PDFのページめくりが、、、
▲開発時に資料を作っておかなかったらもっと大変、、、、、
iTunes/Musicのライブラリに入っている楽曲データのジャンル分けのゆらぎ吸収を行う、ジャンル名のいわゆる「名寄せ」を行うAppleScriptです。
▲1つのアルバム中でもジャンル名に表記ゆれがあるのは勘弁してほしい
iTunes/Musicライブラリ中の全楽曲の最終再生日時を24時間の各時で集計を行い、ジャンルごとに再生時間に偏りがないかどうかを分析するため、さらにジャンルごろに集計してみました。
「名寄せ」(なよせ)は昔から情報処理に見られる泥臭い地道な処理で、データ上の表記ゆれを吸収するための処理です。地名などの固有名詞で多く見られます(念のためにWikipediaで調べたら、異なる業界では割とブラックな言葉として用いられている模様)。
このジャンル名が英語で入っていたり日本語で入っていたりと、ゆらぎが発生しているので、同様のジャンル名であれば同じものとして合算して集計してみました。
また、英語で表記されたジャンル名ではなく日本語で表記されたものを採用(=すべてアルファベットで書かれたものを不採用)しています。まだ、それほどいじめ抜いていない(自分の環境でしかテストしていない)ので、ジャンル名の名寄せリストはもう少し鍛えておく必要があるものと思っています。
AppleScript名:ジャンル名名寄せ v2 |
— – Created by: Takaaki Naganoya – Created on: 2019/11/24 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.5" — El Capitan (10.11) or later use framework "Foundation" use scripting additions property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property NSScanner : a reference to current application’s NSScanner property NSPredicate : a reference to current application’s NSPredicate property NSMutableCharacterSet : a reference to current application’s NSMutableCharacterSet –ジャンル名寄せリスト(2要素から構成される2D List) set genreNayoseList to {{"World", "ワールド"}, {"Anime", "アニメ"}, {"Electronic", "エレクトロニック"}, {"R&B/ソウル", "R&B/ソウル"}, {"Kayokyoku", "歌謡曲"}, {"Electronic", "エレクトロニック"}, {"Vocal", "ヴォーカル"}, {"Classical", "クラシック"}, {"Dance", "ダンス"}, {"Soundtrack", "サウンドトラック"}} –名寄せ対象リスト(genreNameを名寄せ) set aList to {{genreName:"シンガーソングライター", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 1, 5, 0, 0, 0, 0}}, {genreName:"ロック", playingList:{2, 2, 0, 0, 0, 1, 0, 0, 4, 3, 9, 5, 11, 11, 22, 19, 48, 17, 28, 15, 2, 11, 13, 1}}, {genreName:"Soundtrack", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}}, {genreName:"World", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}}, {genreName:"演歌", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"ホリデーミュージック", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"ディズニー", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0}}, {genreName:"J-Pop", playingList:{4, 0, 1, 1, 2, 1, 5, 3, 2, 3, 27, 19, 14, 26, 17, 20, 28, 32, 40, 25, 10, 10, 6, 6}}, {genreName:"ヒップホップ/ ラップ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"Electronic", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"アニメ", playingList:{7, 1, 0, 0, 7, 2, 0, 0, 0, 9, 23, 45, 17, 10, 17, 50, 48, 65, 53, 27, 12, 16, 23, 45}}, {genreName:"ポップ", playingList:{3, 0, 0, 0, 0, 0, 0, 3, 1, 6, 2, 9, 7, 0, 11, 7, 8, 10, 7, 5, 3, 1, 0, 1}}, {genreName:"Kayokyoku", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}}, {genreName:"ヒップホップ/ラップ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"Dance", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}}, {genreName:"ダンス", playingList:{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0}}, {genreName:"R&B/ソウル", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"Anime", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0}}, {genreName:"チルドレン・ミュージック", playingList:{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"Classical", playingList:{1, 1, 0, 0, 2, 1, 8, 1, 0, 0, 11, 5, 2, 0, 4, 9, 13, 2, 2, 0, 0, 0, 0, 0}}, {genreName:"ニューエイジ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0}}, {genreName:"Pop", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}}, {genreName:"ヴォーカル", playingList:{0, 0, 0, 0, 0, 1, 0, 0, 0, 6, 7, 0, 0, 1, 6, 0, 0, 3, 1, 0, 3, 0, 0, 0}}, {genreName:"サウンドトラック", playingList:{23, 31, 0, 0, 0, 2, 1, 0, 7, 7, 27, 29, 26, 25, 28, 48, 66, 76, 56, 16, 8, 31, 6, 10}}, {genreName:"歌謡曲", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 2, 4, 5, 5, 4, 6, 6, 7, 1, 0, 0, 0}}, {genreName:"ホリデー", playingList:{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0}}, {genreName:"Folk", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, {genreName:"ジャズ", playingList:{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 16, 7, 3, 9, 0, 5, 10, 20, 10, 2, 1, 0, 0}}, {genreName:"エレクトロニック", playingList:{0, 1, 3, 0, 0, 0, 0, 1, 4, 13, 8, 13, 2, 18, 6, 13, 10, 16, 25, 7, 8, 0, 2, 10}}, {genreName:"ワールド", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 5, 5, 7, 6, 0, 0, 0, 0, 0}}, {genreName:"インストゥルメンタル", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}}, {genreName:"Vocal", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"クラシック", playingList:{0, 0, 1, 1, 0, 3, 2, 0, 4, 3, 13, 11, 24, 1, 21, 14, 24, 38, 21, 7, 5, 8, 4, 5}}, {genreName:"オルタナティブ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 6, 9, 0}}, {genreName:"R&B/ソウル", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}} set bList to genreNayoseAndUnify(aList, genreNayoseList) of me –> {{genreName:"シンガーソングライター", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 1, 5, 0, 0, 0, 0}}, {genreName:"ロック", playingList:{2, 2, 0, 0, 0, 1, 0, 0, 4, 3, 9, 5, 11, 11, 22, 19, 48, 17, 28, 15, 2, 11, 13, 1}}, {genreName:"サウンドトラック", playingList:{23, 31, 0, 0, 0, 2, 1, 0, 7, 7, 28, 29, 26, 25, 28, 48, 66, 77, 56, 16, 8, 31, 6, 10}}, {genreName:"ワールド", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 5, 5, 8, 6, 0, 0, 0, 0, 0}}, {genreName:"演歌", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"ホリデーミュージック", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"ディズニー", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0}}, {genreName:"J-Pop", playingList:{4, 0, 1, 1, 2, 1, 5, 3, 2, 3, 27, 19, 14, 26, 17, 20, 28, 32, 40, 25, 10, 10, 6, 6}}, {genreName:"ヒップホップ/ ラップ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"エレクトロニック", playingList:{0, 1, 3, 0, 0, 0, 0, 1, 4, 13, 8, 13, 2, 19, 6, 13, 10, 16, 25, 7, 8, 0, 2, 10}}, {genreName:"アニメ", playingList:{7, 1, 0, 0, 7, 2, 0, 0, 0, 9, 23, 52, 17, 10, 17, 50, 49, 67, 54, 27, 12, 16, 23, 45}}, {genreName:"ポップ", playingList:{3, 0, 0, 0, 0, 0, 0, 3, 1, 6, 2, 9, 7, 0, 11, 7, 8, 10, 7, 5, 3, 1, 0, 1}}, {genreName:"歌謡曲", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 2, 4, 5, 5, 4, 6, 7, 7, 1, 0, 0, 0}}, {genreName:"ヒップホップ/ラップ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"ダンス", playingList:{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 0, 0}}, {genreName:"R&B/ソウル", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"チルドレン・ミュージック", playingList:{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {genreName:"クラシック", playingList:{1, 1, 1, 1, 2, 4, 10, 1, 4, 3, 24, 16, 26, 1, 25, 23, 37, 40, 23, 7, 5, 8, 4, 5}}, {genreName:"ニューエイジ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 12, 0, 0, 0, 0}}, {genreName:"Pop", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}}, {genreName:"ヴォーカル", playingList:{0, 0, 0, 0, 0, 1, 0, 0, 0, 6, 7, 0, 0, 1, 6, 1, 0, 3, 1, 0, 3, 0, 0, 0}}, {genreName:"ホリデー", playingList:{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0}}, {genreName:"Folk", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, {genreName:"ジャズ", playingList:{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 16, 7, 3, 9, 0, 5, 10, 20, 10, 2, 1, 0, 0}}, {genreName:"インストゥルメンタル", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}}, {genreName:"オルタナティブ", playingList:{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 6, 9, 0}}} on genreNayoseAndUnify(aList as list, genreNayoseList as list) set gList to FlattenList(genreNayoseList) of me set didProc to {} set a2List to {} repeat with i in aList set aGenre to genreName of i if (aGenre is in gList) and (aGenre is not in didProc) then repeat with ii in genreNayoseList set jj to contents of ii if aGenre is in jj then copy jj to {g1, g2} if chkAlphabet(g1) of me = true then set targG to g2 set targG2 to g1 else set targG to g1 set targG2 to g2 end if set s1Res to searchByGenreName(aList, targG) of me set s2Res to searchByGenreName(aList, targG2) of me set s3Res to addMutipleLists({s1Res, s2Res}) of me set outRec to {genreName:targG, playingList:s3Res} set the end of didProc to g1 set the end of didProc to g2 exit repeat end if end repeat set the end of a2List to outRec else if (aGenre is not in didProc) then set the end of a2List to contents of i end if end if end repeat return a2List end genreNayoseAndUnify on searchByGenreName(aList as list, aGenreName as string) set predicatesStr to "genreName == ’" & aGenreName & "’" set anArray to (NSArray’s arrayWithArray:aList) set aPred to (NSPredicate’s predicateWithFormat:predicatesStr) set bRes to (anArray’s filteredArrayUsingPredicate:aPred) set bbRes to first item of bRes return (playingList of bbRes) as list end searchByGenreName on addMutipleLists(s1List) script spdAdd property s1List : {} property s3List : {} end script copy s1List to (s1List of spdAdd) –init set s1Len to length of first item of (s1List of spdAdd) set (s3List of spdAdd) to makeZero1DList(s1Len, 0) of me repeat with i in (s1List of spdAdd) set tmpLen to length of i if tmpLen is not equal to s1Len then return false repeat with ii from 1 to s1Len set tmp1 to contents of item ii of (s3List of spdAdd) set tmp2 to contents of item ii of i set item ii of (s3List of spdAdd) to (tmp1 + tmp2) end repeat end repeat return (s3List of spdAdd) end addMutipleLists –指定要素を指定回数追加したリストを作成する on makeZero1DList(itemMax, itemElem) set allData to {} repeat itemMax times set the end of allData to itemElem end repeat return allData end makeZero1DList –By Paul Berkowitz –2009年1月27日 2:24:08:JST –Re: Flattening Nested Lists on FlattenList(aList) set oldDelims to AppleScript’s text item delimiters set AppleScript’s text item delimiters to {"????"} set aString to aList as text set aList to text items of aString set AppleScript’s text item delimiters to oldDelims return aList end FlattenList — アルファベットのみか調べて返す on chkAlphabet(checkString) set aStr to NSString’s stringWithString:checkString set allCharSet to NSMutableCharacterSet’s alloc()’s init() allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "a", 26)) allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "A", 26)) set aBool to my chkCompareString:aStr baseString:allCharSet return aBool as boolean end chkAlphabet on chkCompareString:checkString baseString:baseString set aScanner to NSScanner’s localizedScannerWithString:checkString aScanner’s setCharactersToBeSkipped:(missing value) aScanner’s scanCharactersFromSet:baseString intoString:(missing value) return (aScanner’s isAtEnd()) as boolean end chkCompareString:baseString: |
2つの1D List(1次元配列)同士を加算するAppleScriptです。
よく書き捨てていますが、AppleScriptにそんな命令はないのでループで加算することになります。
最近では、Musicの楽曲ライブラリ中の曲の、カテゴリごとの最終再生日時の分布集計を行なったときに、「Classical」「クラシック」などの表記ゆれを吸収するために、これらの複数カテゴリのデータを合成するときに使いました。
AppleScript名:1D Listの加算 |
set s1List to {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0} set s2List to {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 2, 4, 5, 5, 4, 6, 6, 7, 1, 0, 0, 0} set sRes to addTwoList(s1List, s2List) of me –> {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 2, 4, 5, 5, 4, 6, 7, 7, 1, 0, 0, 0} on addTwoList(s1List, s2List) set s3List to {} set s1Len to length of s1List set s2Len to length of s2List if s1Len is not equal to s2Len then return false repeat with ii from 1 to s1Len set tmp1 to contents of item ii of s1List set tmp2 to contents of item ii of s2List set the end of s3List to (tmp1 + tmp2) end repeat return s3List end addTwoList |
書き捨てレベルだとこんな感じ(↑)ですが、今後使い回すことを考えて書いておくと、こんな感じ(↓)でしょうか。データ数が増えたときの対策と、複数の1D Listを連続して大量に加算する必要が発生した際への対策を行なっています。
AppleScript名:1D List同士の加算 v2 |
set s1List to {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 2, 4, 5, 5, 4, 6, 6, 7, 1, 0, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}
set sRes to addMutipleLists(s1List) of me –> {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 5, 3, 5, 6, 7, 6, 8, 8, 8, 2, 1, 1, 1} on addMutipleLists(s1List) script spdAdd property s1List : {} property s3List : {} end script copy s1List to (s1List of spdAdd) –init set s1Len to length of first item of (s1List of spdAdd) set (s3List of spdAdd) to makeZero1DList(s1Len, 0) of me repeat with i in (s1List of spdAdd) set tmpLen to length of i if tmpLen is not equal to s1Len then return false repeat with ii from 1 to s1Len set tmp1 to contents of item ii of (s3List of spdAdd) set tmp2 to contents of item ii of i set item ii of (s3List of spdAdd) to (tmp1 + tmp2) end repeat end repeat return (s3List of spdAdd) end addMutipleLists –指定要素を指定回数追加したリストを作成する on makeZero1DList(itemMax, itemElem) set allData to {} repeat itemMax times set the end of allData to itemElem end repeat return allData end makeZero1DList |
ツリー状のデータ構造をNSBrowserで表示するためのデータ抽出を行うAppleScriptです。
NSBrowserでツリー構造のデータを抽出しつつ表示するインタフェースを作るために、その基盤部分となるツリー状のデータを絞り込むプログラムを書いてみました。
やりたいことをそのまま実装してみたもので、著しくどこかに同じ機能を持つものが存在していそうな気がします(NSTreeControllerとか)。
やりたいことは、現在のツリー構造上のアドレスをもとに、2次元配列で与えたツリー構造データ(データそのものがツリー構造なのではなくて、「アドレス情報」文字列がツリー構造になっている普通の2次元配列)から、
{{"1", "File"}, {"2", "Edit"}, {"3", "View"}, {"4", "Search"}, {"1.1", "New Script"}, {"1.2", "New Script From Template"}, {"1.3", "New Script Tab"}, {"1.4", "New Script Tab From Template"}, {"1.5", "Open..."}, {"1.6", "Open Quickly"}, {"1.6.1", "aaaaaaa"}, {"1.6.2", "bbbbbb"}, {"1.6.3", "ccccccc"}, {"1.6.4", "ddddddd"}, {"1.6.5", "eeeeeeeee"}}
該当するツリーアドレスの同一ブランチに存在するデータ一覧を取得するだけなので(日本語でも表現できているかどうか怪しい)、そこの部分だけ実装してテストしてみたものです。
Query List Item | Result |
---|---|
{} | –> {"File", "Edit", "View", "Search"} |
{1} | –> {"New Script", "New Script From Template", "New Script Tab", "New Script Tab From Template", "Open…", "Open Quickly"} |
{1,6} | –> {"aaaaaaa", "bbbbbb", "ccccccc", "ddddddd", "eeeeeeeee"} |
よその処理系で見かけた処理なので、広く普遍的に存在していそうな処理ではあるものの、探してみるとなかなかお手軽なものが見つからなかったので、自分で作った次第です。
AppleScript名:treeVar.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/24 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions property NSArray : a reference to current application’s NSArray property NSPredicate : a reference to current application’s NSPredicate set treeKeyList to {{"1", "File"}, {"2", "Edit"}, {"3", "View"}, {"4", "Search"}, {"1.1", "New Script"}, {"1.2", "New Script From Template"}, {"1.3", "New Script Tab"}, {"1.4", "New Script Tab From Template"}, {"1.5", "Open…"}, {"1.6", "Open Quickly"}, {"1.6.1", "aaaaaaa"}, {"1.6.2", "bbbbbb"}, {"1.6.3", "ccccccc"}, {"1.6.4", "ddddddd"}, {"1.6.5", "eeeeeeeee"}} set keyList to {1} set tRes to filterTreeKeyArrayByAddr(treeKeyList, keyList) of me –> {"New Script", "New Script From Template", "New Script Tab", "New Script Tab From Template", "Open…", "Open Quickly"} set keyList to {1, 6} set tRes to filterTreeKeyArrayByAddr(treeKeyList, keyList) of me –> {"aaaaaaa", "bbbbbb", "ccccccc", "ddddddd", "eeeeeeeee"} on filterTreeKeyArrayByAddr(aList, keyList) set queryStr to "" repeat with i in keyList set queryStr to queryStr & (contents of i) & "." end repeat set queryStr to queryStr & "[0-99]" set predicatesStr to "SELF[0] MATCHES ’" & queryStr & "’" set anArray to NSArray’s arrayWithArray:aList set aPred to NSPredicate’s predicateWithFormat:predicatesStr –"SELF[0] MATCHES ’1.[0-99]’" set bRes to (anArray’s filteredArrayUsingPredicate:aPred) set bList to {} repeat with i in (bRes as list) set aTmp to last item of i set the end of bList to aTmp end repeat return bList end filterTreeKeyArrayByAddr |
macOS標準搭載のテキストエディタ、「テキストエディット」(TextEdit)をコントロールして、最前面のウィンドウの書類から、指定のフォント名とサイズに該当するテキストを抽出するAppleScriptです。
テキストエディットはApple純正のアプリケーションの割にはAppleScriptサポート機能がよくできていて、選択範囲(selection)を取得できないことをのぞけば、けっこう使えます。
テキストエディットのAppleScript系の機能で最も重要なものは、書類からの書式取得機能(attribute run)です。
▲最前面の書類がリッチテキストフォーマットになっている必要があります
▲Github上のGPUImageのフィルタ紹介文からコピペしてきた文章
▲この書類のこのレベルの見出しだけを抽出したい場合にはHelvetica Neue Bold 20pointを指定
▲この書類のこのレベルの見出しだけを抽出したい場合にはHelvetica Neue Bold 16pointを指定
Webブラウザなどからコピペしてきたスタイル付きテキストから、特定のフォントや文字サイズの箇所を抽出したい場合に使います。
実行すると、リッチテキストで使用されているフォント名とサイズの一覧を生成し、ダイアログでフォント名の選択、
そして、文字サイズの選択を実行。
すると、該当箇所のみ抽出して返します。さらに改行で区切ったテキストに変換するとかいった処理を行なってもよいでしょう。
RTFのファイルを処理するのであれば、テキストエディットを使わなくてもCocoaの機能を用いて同様の処理を行えますが、割と書き捨てレベルの瑣末なScriptなのでテキストエディット経由で書式情報を取得するように組んでみました。
AppleScript名:指定のフォントとサイズに該当するテキストを抽出する |
— Created 2019-11-22 by Takaaki Naganoya — 2019 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" script spd property fontList : {} property fontSizes : {} property textList : {} property outList : {} end script set (outList of spd) to {} set (fontList of spd) to {} set (fontSizes of spd) to {} set (textList of spd) to {} –TextEdit書類から書式情報を根こそぎ取得 tell application "TextEdit" tell text of front document set (fontList of spd) to font of every attribute run –フォント名 set (fontSizes of spd) to size of every attribute run –文字サイズ set (textList of spd) to character of every attribute run –文字 end tell end tell –取得したフォント名一覧から重複部分を除去してどれを対象にするかユーザーに問い合わせ set fRes to uniquify1DList((fontList of spd), true) of me set targFont to choose from list fRes set aFont to contents of first item of targFont –取得したフォントサイズ一覧から重複部分を除去してどれを対象にするかユーザーに問い合わせ set sRes to uniquify1DList((fontSizes of spd), true) of me set targSize to choose from list sRes set aSize to contents of first item of targSize –取得した条件にもとづいて、根こそぎ取得した書式と照合を行いつつループ repeat with i from 1 to length of (fontList of spd) set tmpFont to contents of item i of (fontList of spd) set tmpSize to contents of item i of (fontSizes of spd) –文字サイズをざっくり判定することで「大、中、小」といったおおまかな切り分けも可能 if {tmpFont, tmpSize} = {aFont, aSize} then set tmpCon to (contents of item i of (textList of spd)) as string if tmpCon does not contain "•" then –ごみ取り set the end of (outList of spd) to tmpCon end if end if end repeat return (outList of spd) –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" if aBool = true then return bArray as list else return bArray end if end uniquify1DList |
オープンソースのGPUImage.framework(GPUImage1)はAppleScriptから呼び出すのも簡単ですし、機能セットも充実しており、非常に重宝して使っています。
ただし、GPUImage自体はSwiftで書き直されたGPUImage2、OSの変更を受けてOpenGLではなくMetalを用いるように書き直され、いまだ発展途上のGPUImage3と変化してきました。
・GPUImageのバージョンと搭載フィルタ数の推移
GPUImage1:■■■■■■■■■■■■ 125 GPUImage2:■■■■■■■■■■ 100 GPUImage3:■■■■ 45
GPUImage2/3とバージョンが進むにつれて搭載フィルタ数が減っており、かつGPUImage2以降はAppleScriptから呼べないため、GPUImage3は(自分の)移行先になりません。
Objective-CベースのGPUImage1系のプログラムを使い続けるという判断をしている開発者は多いようで、Github上で派生プログラムが大量にみつかります。ただ、GPUImage1系はmacOSがOpenGLを完全廃止にしたとたんに動かなくなるはずです。
このため、GPUImage1系に依存しているプログラムは別の部品を呼び出して処理する必要が出てきます。
一番有望なのは、macOS標準搭載のCIFilter。CIFilterについては、実際にプログラムを組んでみて「組みにくい」という印象を受けていました(機能はちゃんとしていそうなものの、機能数にドキュメントが見合っていません)。あと、AppleScriptからパラメータを指定しにくいという印象を受けていました(パラメータに指定するオブジェクトにAppleScriptから直接指定できないものがあるので、少しラッパーを書く必要がある)。
その後、各種フィルタを呼び出す汎用ルーチンを整備することで、利用難易度を下げる努力を自主的に行なっています。
・GPUImage3とCoreImageの搭載フィルタ数の比較
GPUImage3:■■■■ 45 CoreImage:■■■■■■■■■■■■■■■■■ 174
そこで、実際に処理速度の面でどうなのか、CIFilterとGPUImageを比較するとどうなのか、調べてみました。
まずは、ざっくりありもののルーチンを引っ張り出してきて実行。どちらも期待どおりの動作をしてくれます。ただし、周辺ルーチンの仕様が違っていたのでそこも合わせます。
乱数ファイル名を作成する定番のshell commandの「uuidgen」はお手軽な反面、呼び出すのに時間がかかるので(手元の時間で0.1秒程度強)、より高速なCocoaのUUIDStringを用いる部品に置き換えました。
それぞれのフレームワークごとの挙動の違いもあります。
経験上、GPUImage.frameworkは初回実行時だけは時間が長くかかる傾向があり(1秒以上かかる)、2回目以降はより短い時間で処理できるようになることを確認しています。一方、CIFilterは初回およびそれ以降の呼び出しでもさほど処理時間が変わりません。
これら2つを実際に同じデータで同一条件下で比較してみたところ、同一の処理(画像の差分計算)を行なったかぎりでは処理時間に差はほとんど有意な差はありませんでした。
Script Geek 2.0.1によるScriptの処理時間比較
First:GPUImage Version , Second:CoreImage Version MacBookPro10,1, macOS Version 10.14.6 (Build 18G2012), 10 iterations First Run Total Time Average First 0.969 1.194 0.119 Second 0.121 1.221 0.122 Ratio (excluding first run): 1:1.02
自分のメインマシン(MacBook Pro Retina 2012、Core i7 2.66GHz、RAM 8GB)ではどちらもテストデータ(1192x1684pixel 8bit color image x 2)を0.1〜0.2秒程度で処理できています。個人的には十分なスピードが出ていると感じていますが、不満がある方はデータをRAM Disk上に置いたり、並列処理するとよいのではないでしょうか(シングルスレッド処理が高速なマシンを用意するというのが一番効きそうではあります。iMac 5Kの上位モデルとか)。
すべての類似フィルタで比較を行なったわけではありませんが(どれとどれが類似、という情報を整理するのに手間がかかる)、一番使っている「空白検出のためのヒストグラム計算フィルタ」はGPUImageにしか存在しませんでした(作成当時の話で現在はCoreImageにもCIAreaHistogramなどの機能があります)。
空白検出処理を高速に行えることで巨大なメリットが生じています。用途については枚挙にいとまがありませんが、Markdown書類をPDFにレンダリングした場合に、末尾に無用な空白のページが生じるケースがあり、これを検出しつつ末尾のページを自動削除することで、膨大な無駄手間を削減できています。空白検出処理を高速に行う手段を用意していないと、Photoshopで指定ページをレンダリングしてヒストグラムを計算する必要があるわけで、Photoshopのない環境に進出できなくなります。
空白画像の検出処理はGPUImage.framework抜きで(AppleScriptだけで)2〜3倍高速な処理を行える部品「画像の空白判定 v3」を作成済みなので、GPUImageなしでもさほど困りません。
▲同一環境にて、Photoshop CC 2018、GPUImage、AppleScriptだけで処理している「画像の空白判定 v3」で各種サイズの画像の空白検出を実行(単位:秒)
Objective-C/Swift系の開発者の方々に「GPUImageを経由してGPUのパワーを使って処理するよりも、AppleScript単体で空白の比較演算処理したほうが速かった」という事実をお話しすると、最初は「嘘つけ」というひきつった笑顔の反応なのですが、実際にコードを動かして動作原理を丁寧に説明すると分かっていただけるようです。
–> Download dffFilterDemoPrograms(Two Code-Signed AppleScript Applet Executables with Framework in its bundle)
AppleScript名:CoreImageでCIDifferenceBlendMode.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/22 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use framework "QuartzCore" use scripting additions property CIFilter : a reference to current application’s CIFilter property NSUUID : a reference to current application’s NSUUID property |NSURL| : a reference to current application’s |NSURL| property CIImage : a reference to current application’s CIImage property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSPNGFileType : a reference to current application’s NSPNGFileType property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep set mePath to POSIX path of (path to me) set aImg to mePath & "Contents/Resources/book1_v2.0_0011.jpg" set bImg to mePath & "Contents/Resources/book1_v2.1_0011.jpg" –https://developer.apple.com/library/archive/documentation/ –GraphicsImaging/Reference/CoreImageFilterReference/index.html –#//apple_ref/doc/filter/ci/CIDifferenceBlendMode set cifilterName to "CIDifferenceBlendMode" set imgRes to filterBlendImageFiles(aImg, bImg, cifilterName) of me set outPath to retUUIDfilePath(POSIX path of (path to desktop), "png") of me set sRes to saveNSImageAtPathAsPNG(imgRes, outPath) of me –CIFilterをかけたJPEG画像を生成 on filterBlendImageFiles(aPath, bPath, aFilterName) –CIImage(A)を生成 set aURL to |NSURL|’s fileURLWithPath:aPath –Input set aCIImage to CIImage’s alloc()’s initWithContentsOfURL:aURL –CIImage(B)を生成 set bURL to |NSURL|’s fileURLWithPath:bPath –Input set bCIImage to CIImage’s alloc()’s initWithContentsOfURL:bURL — CIFilter をフィルタの名前で生成 set aFilter to CIFilter’s filterWithName:aFilterName aFilter’s setDefaults() –各フィルタのパラメータはデフォルト –Blend Filterを実行 aFilter’s setValue:(aCIImage) forKey:"inputImage" aFilter’s setValue:(bCIImage) forKey:"inputBackgroundImage" set aOutImage to aFilter’s valueForKey:"outputImage" set outNSImage to convCIimageToNSImage(aOutImage) of me return outNSImage end filterBlendImageFiles on retUUIDfilePath(aPath, aEXT) set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string set aPath to ((NSString’s stringWithString:aPath)’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT return aPath end retUUIDfilePath 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 –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 |
AppleScript名:(比較用)GPUImageによる差分計算.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/22 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use scripting additions use framework "Foundation" use framework "AppKit" use framework "GPUImage" –https://github.com/BradLarson/GPUImage property NSUUID : a reference to current application’s NSUUID property |NSURL| : a reference to current application’s |NSURL| property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSPNGFileType : a reference to current application’s NSPNGFileType property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep set mePath to POSIX path of (path to me) set aImagePath to mePath & "Contents/Resources/book1_v2.0_0011.jpg" set bImagePath to mePath & "Contents/Resources/book1_v2.1_0011.jpg" set imgRes to composeImageWithBlendFilter(aImagePath, bImagePath, "GPUImageDifferenceBlendFilter") of me set outPath to retUUIDfilePath(POSIX path of (path to desktop), "png") of me set sRes to saveNSImageAtPathAsPNG(imgRes, outPath) of me on composeImageWithBlendFilter(aImagePath, bImagePath, filterName) set aImage to NSImage’s alloc()’s initWithContentsOfFile:aImagePath set bImage to NSImage’s alloc()’s initWithContentsOfFile:bImagePath set aClass to current application’s NSClassFromString(filterName) set blendFilter to aClass’s alloc()’s init() set pictureA to current application’s GPUImagePicture’s alloc()’s initWithImage:aImage pictureA’s addTarget:blendFilter pictureA’s processImage() set imgRes to blendFilter’s imageByFilteringImage:bImage return imgRes end composeImageWithBlendFilter on retUUIDfilePath(aPath, aEXT) set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string set aPath to ((NSString’s stringWithString:aPath)’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 |
macOS 10.13のBetaの時にはなかったのに、Release版がバグだらけでリリース。PDFViewについて多大なる被害を受けております。PDFViewをScripting Bridge経由で使うと、macOS 10.14も10.15も状況は同じです。きちんと動かないので、仕様と言い切ることは難しいところでしょう。未修正のバグが放置されているという状況です。
Developper Supportに報告しても返答も何もないので、PDFViewのScripting Bridgeまわりはバグだらけのまま直すつもりもないのでしょう。Xcode上でプログラムを組みだすと、腹の立つことのオンパレードです。
AppleのDevelopperライセンスの延長時期がやってくると、日頃からバグだらけでOSをリリースするわバグは直さないわで、嫌がらせを受け続けているのになんでこれを契約延長しなくてはならないのか理解に苦しみます。
Apple系のMailing Listでは主流ともいえるCocoa-Dev ML上でもAppleのあり方に疑問を呈したり文句を(ていねいなお言葉で)述べる投稿がここ数週間(macOS 10.15のリリース以降)激増している今日このごろです。
いまいちどPDFViewまわりを確認してみると、PDFViewのcurrentPageを取得できない未修正の特大バグに続いて、goToPreviousPage、goToNextPageも効きません。つまり、現在表示中のページの取得も、前後ページへの移動もできないという状況です。
▲まともにオブジェクトを返してこないcurrentPage
とりあえず、Xcode上でヘッダーファイル「PDFView.h」を確認していると、currentPageのかわりになりそうなものを見つけました。それが、visiblePages。PDFView上で表示中のページを(複数)返してくるわけで、単ページ表示時にはなんとかこれが使えるかも? ということで試してみました。
–> Download pdfviewAgainstAppleBug (Xcode Project)
▲currentPageのかわりに使えそうなvisiblePages。こっちは機能している
これが使えたので応用し、かなり遠回りな処理にはなりますが、表示中のページの前後移動もできるようにしてみました。
ただ、PDFViewをもとにしたクリックスルーなPDFViewを使ってみると、いまひとつ想定どおりに動作してくれません。まだいろいろ試してみる必要がありそうです。
→ クリックスルーPDFViewは結局動作するようになり、Double PDF v2に実装できました
これらのほか、PDFViewはページ変更時のイベントやドキュメント変更時のイベントが送信されないなど、苦労させられまくりです。
指定ファイルからのメタデータ情報の取得を行うAppleScriptです。
メタデータの取得はshell commandの「mdls」で行えます。ほぼそれと同じ内容です。ループですぺての属性値を取得するか、属性ラベルを指定して個別に属性値を取り出すことになります。
この方法でメタデータの書き込みや追記が行えないかと調べてみましたが、どうもできないようで「自前でMDImporterを作れ」とか言われてもテーマが大きすぎて困ります……
メタデータの書き込みはshell commandのほうが手っ取り早そうです。
AppleScript名:ファイルからのメタデータ情報の取得 v1scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/21 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSMetadataItem : a reference to current application’s NSMetadataItem set aFile to POSIX path of (choose file) set aURL to |NSURL|’s fileURLWithPath:aFile set aMetaInfo to NSMetadataItem’s alloc()’s initWithURL:aURL set attrList to (aMetaInfo’s attributes()) as list set metaList to {} repeat with i in attrList set j to contents of i set aTmpVal to (aMetaInfo’s valueForAttribute:(j)) set the end of metaList to {j as string, aTmpVal as {number, string, date, list}} end repeat return metaList |
AppleScript名:ファイルからのメタデータ情報の取得 v2a.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/21 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSMetadataItem : a reference to current application’s NSMetadataItem set aFile to POSIX path of (choose file) set aURL to |NSURL|’s fileURLWithPath:aFile set aMetaInfo to NSMetadataItem’s alloc()’s initWithURL:aURL –fixed –指定した属性ラベルの値だけ一括で取り出す set metaDict to (aMetaInfo’s valuesForAttributes:{"kMDItemContentTypeTree", "kMDItemContentType"}) as record –> (* (NSDictionary) { kMDItemContentTypeTree:{ "com.apple.application-bundle", "com.apple.localizable-name-bundle", "com.apple.application-bundle", "public.directory", "public.executable", "com.apple.application", "public.item", "com.apple.package", "com.apple.bundle" }, kMDItemContentType:"com.apple.application-bundle" } *) |
世界地図のSVGをHTMLReader.frameworkでparseして、目的のノードに塗り色の指定を行い、WkWebView上で表示するAppleScriptです。
–> Download selectCountry2(Code-signed AppleScript executable applet)
昨日のものがあくまでSVGのParseと表示が目的の試作品であったのに対し、本Scriptは連続してポップアップメニューから対象を選んで表示するというものです。
同様にmacOS 10.15.1上で作成しましたが、このバージョンのOS環境ではAppleScriptアプレットにFrameworkを入れてAppleScript内から呼び出すことが標準のランタイム(Script Editorから書き出した場合)では行えないようになっており、Script Debuggerから「Application(Enhanced)」で書き出さないと呼び出せないようです(これはちょっとひどいかも、、、、)。
世界地図はWikipediaから拾ってきたもので、ISO国コードではなく名称でIDが振ってあり、日本もJapanではなくHokkaido、Honshu、Shikoku、Kyushuuに分けて登録されています。
▲Script Editor上でサードパーティFramework呼び出しの動作ができているのは、この実行環境でSIPを解除してあるからです
AppleScript名:アラートダイアログ上のWebViewに世界地図を表示 v2b.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/18 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use framework "HTMLReader" –https://github.com/nolanw/HTMLReader use framework "WebKit" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSAlert : a reference to current application’s NSAlert property NSColor : a reference to current application’s NSColor property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSScreen : a reference to current application’s NSScreen property NSButton : a reference to current application’s NSButton property WKWebView : a reference to current application’s WKWebView property WKUserScript : a reference to current application’s WKUserScript property NSURLRequest : a reference to current application’s NSURLRequest property HTMLDocument : a reference to current application’s HTMLDocument property NSRunningApplication : a reference to current application’s NSRunningApplication property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property WKUserContentController : a reference to current application’s WKUserContentController property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd property theResult : 0 property returnCode : 0 property aWebView : missing value property aLog : missing value property tmpURL : "" tell current application set mePath to ((path to me) as string) & "Contents:Resources:World_map_-_low_resolution.svg" set svgCon to read (mePath as alias) set aWidth to 950 set aHeight to 650 set paramObj to {myMessage:"Browse World map", mySubMessage:"This is a sample SVG World map", targContents:svgCon, myWidth:(aWidth), myHeight:(aHeight)} –my browseLocalWebContents:paramObj my performSelectorOnMainThread:"browseLocalWebContents:" withObject:(paramObj) waitUntilDone:true return aLog end tell on browseLocalWebContents:paramObj set aMainMes to (myMessage of paramObj) as string set aSubMes to (mySubMessage of paramObj) as string set tmpURL to (targContents of paramObj) as string –local contents set aWidth to (myWidth of paramObj) as integer set aHeight to (myHeight of paramObj) as integer set aView to current application’s NSView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight)) –Ppopup Buttonをつくる set a1Button to current application’s NSPopUpButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, aHeight – 26, 200, 24)) pullsDown:(false) a1Button’s removeAllItems() –Menuの中身を作る set aHTML to current application’s HTMLDocument’s documentWithString:(tmpURL) set fList to (aHTML’s nodesMatchingSelector:"path")’s valueForKeyPath:"attributes.id" set fList to fList’s sortedArrayUsingSelector:"compare:" a1Button’s addItemWithTitle:"▼Select" repeat with i in fList (a1Button’s addItemWithTitle:(i as string)) end repeat a1Button’s setTarget:(me) a1Button’s setAction:("mySelector:") a1Button’s setEnabled:(true) set aWebView to makeWebViewAndLoadContents(tmpURL, aWidth, aHeight – 40) of me aView’s addSubview:a1Button aView’s addSubview:aWebView — set up alert set theAlert to NSAlert’s alloc()’s init() tell theAlert its setMessageText:aMainMes its setInformativeText:aSubMes its addButtonWithTitle:"OK" its addButtonWithTitle:"Cancel" its setAccessoryView:aView end tell — show alert in modal loop NSRunningApplication’s currentApplication()’s activateWithOptions:0 my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true –Stop Web View Action set bURL to |NSURL|’s URLWithString:"about:blank" set bReq to NSURLRequest’s requestWithURL:bURL aWebView’s loadRequest:bReq if (my returnCode as number) = 1001 then error number -128 end browseLocalWebContents: on doModal:aParam set (my returnCode) to (aParam’s runModal()) as number end doModal: on viewDidLoad:aNotification return true end viewDidLoad: –Popup Action Handler on actionHandler:sender set aTitle to title of sender as string end actionHandler: on fetchJSSourceString(aURL) set jsURL to |NSURL|’s URLWithString:aURL set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value) return jsSourceString end fetchJSSourceString on mySelector:aSender set targetCountry to (aSender’s title()) as string set aHTML to current application’s HTMLDocument’s documentWithString:(tmpURL as string) set eList to (aHTML’s nodesMatchingSelector:"path") as list set fList to (aHTML’s nodesMatchingSelector:"path")’s valueForKeyPath:"attributes.id" –Search Target Country set aRes to (fList)’s indexOfObject:(targetCountry) if (aRes = current application’s NSNotFound) or (aRes > 9.99999999E+8) then error "Target country not found" end if set aRes to aRes + 1 –Rewrite target country’s fill color on SVG set tmpNode to contents of item aRes of (eList) (tmpNode’s setObject:"Red" forKeyedSubscript:"fill") –Change Target Country’s fill color set svgCon to (tmpNode’s |document|()’s serializedFragment()) as string –HTMLReaderはこういう処理ができるところが好き aWebView’s loadHTMLString:svgCon baseURL:(missing value) end mySelector: on makeWebViewAndLoadContents(aContents as string, aWidth as integer, aHeight as integer) set aConf to WKWebViewConfiguration’s alloc()’s init() –指定URLのJavaScriptをFetch set jsSource to my fetchJSSourceString(aContents) set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true set userContentController to WKUserContentController’s alloc()’s init() userContentController’s addUserScript:(userScript) aConf’s setUserContentController:userContentController set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight)) configuration:aConf aWebView’s setNavigationDelegate:me aWebView’s setUIDelegate:me aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true aWebView’s setOpaque:false aWebView’s setBackgroundColor:(NSColor’s clearColor()) –aWebView’s scrollView()’s setBackgroundColor:(NSColor’s clearColor()) aWebView’s loadHTMLString:aContents baseURL:(missing value) return aWebView end makeWebViewAndLoadContents |
世界地図のSVGをHTMLReader.frameworkでparseして、目的のノードに塗り色の指定を行い、WkWebView上で表示するAppleScriptです。
–> Download countrySel(Code-Signed AppleScript applet executable. Its icon is customized version by Script Debbuger)
▲いかにもなメルカトル図法の世界地図。極地に行くほど巨大に表現される。国によっては国土が小さいために色を変えてもこの縮尺だとわからないことも
SVGをparseするのにXML Parser系のFrameworkを軒並み試してみたのですが、所定の要素を掘り進んでいくまでは楽にできるものの、検索先のnodeまで行って属性値を書き換えたあとでrootまで戻り、属性値を書き換えたXML全体を文字列化するような機能を備えたものは見つかりませんでした。
この手の処理で圧倒的に便利なのがHTMLReader.framework。書き換え要素を検索して、書き換えたあとにrootまで戻り、Document全体を文字列化して返すような処理が楽勝です。この手の処理が楽にできるプログラムは、自分が知っているかぎりではこれだけです。
本ScriptはmacOS 10.15.1上で作って試してみましたが、驚いたことに、アプリケーション書き出ししたときに、Apple純正のランタイムだとAppleScriptアプレット内にバンドルしたフレームワークにアクセスできなくなっていました。
セキュリティを強化したいのは理解できますが、いささかやりすぎではないでしょうか。この先、Script Debuggerを用いて書き出したランタイムまで実行できなくなったとしたらXcode上で作らないと実行できなくなってしまうことでしょう。いささかやりすぎのようにも思えます。
Script Debuggerから「Application(Apple)」で書き出すと、Frameworkにアクセスできないんだか、WkWebViewにアクセスできないんだか不明ですが、macOS 10.15上では地図が表示できません。
▲Script Debugger上でアプレット書き出しするさいに、Applicaion(Enhanced)で書き出さないとバンドル内のフレームワーク呼び出しができなかった。Script Debuggerがないと手も足も出ない
▲ISO国コードから選択。「JP」を選ぶと日本が、「AU」を選ぶとオーストラリアが赤く表示される
▲SVGの世界地図をテキストエディタでオープンしたところ
AppleScript名:SVGの要素を走査するじっけん v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/19 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "HTMLReader" –https://github.com/nolanw/HTMLReader use webDialog : script "webViewLib" property NSString : a reference to current application’s NSString property HTMLDocument : a reference to current application’s HTMLDocument property textArray : missing value property anError : missing value –Choose Target Country set isoCountry to (current application’s NSLocale’s ISOCountryCodes()) as list set cRes to choose from list isoCountry with prompt "Select Target Country" set targetCountry to contents of first item of cRes –Load SVG World map in this bundle set mePath to ((path to me) as string) & "Contents:Resources:world-2.svg" set aData to read (mePath as alias) set aHTML to current application’s HTMLDocument’s documentWithString:(aData as string) set eList to (aHTML’s nodesMatchingSelector:"path") as list set fList to (aHTML’s nodesMatchingSelector:"path")’s valueForKeyPath:"attributes.id" –Search Target Country set aRes to (fList)’s indexOfObject:(targetCountry) if (aRes = current application’s NSNotFound) or (aRes > 9.99999999E+8) then error "Target country not found" end if set aRes to aRes + 1 — (Cocoa array index begins from 0, AppleScript is 1) –Rewrite target country’s fill color on SVG set tmpNode to contents of item aRes of (eList) (tmpNode’s setObject:"Red" forKeyedSubscript:"fill") –Change Target Country’s fill color set svgCon to (tmpNode’s |document|()’s serializedFragment()) as string –HTMLReaderはこういう処理ができるところが好き –Alert Dialog上でSVGを表示 dispWebViewByString(svgCon, "This is " & targetCountry, "This is your indicated country by 2 character country code") of webDialog |
アラートダイアログ上のWebViewにSVGで作った円グラフを表示するAppleScriptです。
Objective-Cで作られたチャート表示フレームワークなどを呼び出して試していますが、スクリプトエディタ上で作成したAppleScriptから呼び出すと、アニメーションしなかったり表示が行えなかったりといろいろ環境にフィットしていません。
# 逆をいえば、Xcode上でCocoa AppleScriptアプリケーションを作成した場合には割とチャート表示部品をそのまま使えます
そこで、単純なグラフ表示を行うようにレベルを下げ、SVGをWkWebView上に表示する形式にしてみました。
本Scriptでは最低限のサンプルを表示させていますが、SVGの組み立てをもう少し強化してグラフの背景を透明にできればいい感じになるのではないでしょうか。
▲世界地図あたりだといい感じに見えるかもしれません。当該の国だけ色を変えるような処理を仕込めば何かに使えそうです。
AppleScript名:アラートダイアログ上のWebViewに円グラフを表示.scptd |
— – Created by: Takaaki Naganoya – Created on: 2019/11/18 — – Copyright © 2019 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use framework "WebKit" use scripting additions property |NSURL| : a reference to current application’s |NSURL| property NSAlert : a reference to current application’s NSAlert property NSColor : a reference to current application’s NSColor property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSScreen : a reference to current application’s NSScreen property NSButton : a reference to current application’s NSButton property WKWebView : a reference to current application’s WKWebView property WKUserScript : a reference to current application’s WKUserScript property NSURLRequest : a reference to current application’s NSURLRequest property NSRunningApplication : a reference to current application’s NSRunningApplication property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property WKUserContentController : a reference to current application’s WKUserContentController property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd property theResult : 0 property returnCode : 0 –set svgCon to read (choose file) set svgCon to retSampleSVG() of me set paramObj to {myMessage:"Browse Pie Chart", mySubMessage:"This is a sample pie chart made by SVG", targContents:svgCon} –my browseLocalWebContents:paramObj my performSelectorOnMainThread:"browseLocalWebContents:" withObject:(paramObj) waitUntilDone:true on browseLocalWebContents:paramObj set aMainMes to (myMessage of paramObj) as string set aSubMes to (mySubMessage of paramObj) as string set tmpURL to (targContents of paramObj) as string –local contents set aWidth to 330 set aHeight to 430 –WebViewをつくる set aConf to WKWebViewConfiguration’s alloc()’s init() –指定URLのJavaScriptをFetch set jsSource to my fetchJSSourceString(tmpURL) set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true set userContentController to WKUserContentController’s alloc()’s init() userContentController’s addUserScript:(userScript) aConf’s setUserContentController:userContentController set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight – 100)) configuration:aConf aWebView’s setNavigationDelegate:me aWebView’s setUIDelegate:me aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true aWebView’s setOpaque:false aWebView’s setBackgroundColor:(NSColor’s clearColor()) –aWebView’s scrollView()’s setBackgroundColor:(NSColor’s clearColor()) aWebView’s loadHTMLString:tmpURL baseURL:(missing value) — set up alert set theAlert to NSAlert’s alloc()’s init() tell theAlert its setMessageText:aMainMes its setInformativeText:aSubMes its addButtonWithTitle:"OK" its addButtonWithTitle:"Cancel" its setAccessoryView:aWebView end tell — show alert in modal loop NSRunningApplication’s currentApplication()’s activateWithOptions:0 my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true –Stop Web View Action set bURL to |NSURL|’s URLWithString:"about:blank" set bReq to NSURLRequest’s requestWithURL:bURL aWebView’s loadRequest:bReq if (my returnCode as number) = 1001 then error number -128 end browseLocalWebContents: on doModal:aParam set (my returnCode) to (aParam’s runModal()) as number end doModal: on viewDidLoad:aNotification return true end viewDidLoad: on fetchJSSourceString(aURL) set jsURL to |NSURL|’s URLWithString:aURL set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value) return jsSourceString end fetchJSSourceString on retSampleSVG() return "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"300\" height=\"300\" fill-opacity=\"0.9\"> <path d=\"M 150,0 A 150,150 0 1,1 141.8986347236027,299.7810664959306 L 150,150 Z\" fill=\"#0153d8\" stroke=\"#fff\"></path> <text x=\"170\" y=\"150\" fill=\"#fff\" font-size=\"20\">Piyomaru</text> <text x=\"170\" y=\"190\" fill=\"#fff\" font-size=\"40\">50.86%</text> </svg>" end retSampleSVG |