Archive for 7月, 2017

2017/07/30 ハードウェアのアイコン一覧から1つを選択してファイル名を取得(拡張子を除去)

OSのリソースフォルダ内からハードウェアアイコンの一覧を取得し、ファイル名を返すAppleScriptです。

拡張子を外しているのは、NSImageにアイコンをロードすることを目的に作成したからです。

本来は、ハードウェアID(MacBook9,1)からアイコン名(com.apple.macbook-retina-gold)を取得するようなサービスがOS内に存在しているとよいのですが、なかなかそのような機能を呼び出すとっかかりが見つかりませんでした(自機のアイコンを求める機能はあるので、きっと近いものはあるはず)。

「じゃあファイル一覧から選択すればよいのでは?」と考え、最終到達点を下げて実装だけ試してみたものです。

mac_hards_resized.png
▲ハードウェアのアイコン一覧

mac_hards2_resized.png
▲選択したアイコンをASCII ART化したところ(1)

mac_hards3_resized.png
▲選択したアイコンをASCII ART化したところ(2)

AppleScript名:ハードウェアのアイコン一覧から1つを選択してファイル名を取得(拡張子を除去)
– Created 2017-07-30 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4753

–ハードウェアのアイコン一覧から1つを選択してファイル名を取得(拡張子を除去)
set hRes to chooseHardwareModel() of me
–>  ”com.apple.macbookpro-15-retina-display”

set anImage to current application’s NSWorkspace’s sharedWorkspace()’s iconForFileType:hRes

on chooseHardwareModel()
  set sRes to (do shell script “ls “ & (quoted form of “/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/”) & “com.apple.*.icns”)
  
set aList to paragraphs of sRes
  
set aaList to choose from list aList
  
if aaList = false then return false
  
set aPath to first item of aaList
  
set aStr to ((current application’s NSString’s stringWithString:aPath)’s lastPathComponent()’s stringByDeletingPathExtension()) as string
  
return aStr
end chooseHardwareModel

★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 NSImageの垂直、水平反転

指定の画像を読み込んでNSImageにして、垂直反転、水平反転するAppleScriptです。

NSImageそのものを回転させるのはもんのすごく大変そうなので、反転だけしてみました。

piyowolf.png
▲オリジナル画像

flipimagevertically.png
▲垂直方向に反転した画像(flipImageVertically)

flipimagehorizontally.png
▲水平方向に反転した画像(flipImageHorizontally)

回転させるなら元画像よりひとまわり大きなNSImageViewを作ってNSImageを設定し、回転系のmethodでも呼び出して、透過部分を自動トリミングすればいいんじゃないか、などと思っています。

あるいは、NSAffineTransformで90度回転、270度回転の状態を作って、そのターゲットサイズの空白画像の上に重ね合わせるとか。とりあえず、参考にするObjective-CのプログラムをWeb上で探してはみるものの、NSImageそのものを回転させる記述例にはあまりお目にかかりません。

以上はあくまで「Cocoaの機能を直接利用した場合の話」であり、一般的な画像回転はOS標準装備のImageEventsを呼び出して手軽に行えます。

→ ImageEventsで画像回転(時計回りに90度)
→ ImageEventsで画像回転(反時計回りに90度)

AppleScript名:NSImageの垂直、水平反転
– Created 2017-07-25 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–https://stackoverflow.com/questions/10936590/flip-nsimage-on-both-axes
–http://piyocast.com/as/archives/4748

set aFile to POSIX path of (choose file of type {“public.image”} with prompt “Select an Image”)

set currentImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:aFile
set imgRes to flipImageHorizontally(currentImage) of me
set fRes to retUUIDfilePath(aFile, “png”) of me
set sRes to saveNSImageAtPathAsPNG(imgRes, fRes) of me

–水平方向の画像反転
on flipImageHorizontally(anNSImage)
  set transform to current application’s NSAffineTransform’s transform()
  
set dimList to anNSImage’s |size|()
  
set flipList to {-1.0, 0.0, 0.0, 1.0, dimList’s width, 0.0}
  
set tmpImage to current application’s NSImage’s alloc()’s initWithSize:(dimList)
  
tmpImage’s lockFocus()
  
transform’s setTransformStruct:flipList
  
transform’s concat()
  
anNSImage’s drawAtPoint:(current application’s NSMakePoint(0, 0)) fromRect:(current application’s NSMakeRect(0, 0, dimList’s width, dimList’s height)) operation:(current application’s NSCompositeCopy) fraction:1.0
  
tmpImage’s unlockFocus()
  
return tmpImage
end flipImageHorizontally

–垂直方向の画像反転
on flipImageVertically(anNSImage)
  set transform to current application’s NSAffineTransform’s transform()
  
set dimList to anNSImage’s |size|()
  
set flipList to {1.0, 0.0, 0.0, -1.0, 0.0, dimList’s height}
  
set tmpImage to current application’s NSImage’s alloc()’s initWithSize:(dimList)
  
tmpImage’s lockFocus()
  
transform’s setTransformStruct:flipList
  
transform’s concat()
  
anNSImage’s drawAtPoint:(current application’s NSMakePoint(0, 0)) fromRect:(current application’s NSMakeRect(0, 0, dimList’s width, dimList’s height)) operation:(current application’s NSCompositeCopy) fraction:1.0
  
tmpImage’s unlockFocus()
  
return tmpImage
end flipImageVertically

on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (current application’s NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((current application’s 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 current application’s NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to current application’s NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(current application’s NSPNGFileType) |properties|:(missing value))
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsPNG

★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/24 ANSI,ASCII ARTを読み込んで画像にレンダリングする

オープンソースのAnsiLove.framework(By Ansilove)を利用して、ANSIアートをPNG画像にレンダリングするAppleScriptです。

あまりお目にかかったことはなかったのですが、ANSIエスケープシーケンスを使った文字ベースのアート

  ANSi (.ANS)
  Binary (.BIN)
  Artworx (.ADF)
  iCE Draw (.IDF)
  Xbin (.XB) details
  PCBoard (.PCB)
  Tundra (.TND) details
  ASCII (.ASC)
  Release info (.NFO)
  Description in zipfile (.DIZ)

をPNG画像にレンダリングするとのこと。実際に探してみるまではお目にかかったことはなかったのですが、DOS時代のテイストの文字ベースのグラフィックです。

ansi_art.png
▲元のエスケープシーケンスのテキストファイル

f3387396-a19d-405b-9986-19e1ec50aad4_resized.png
▲レンダリング結果のPNG画像

実行のためには「AnsiLove.framework」のプロジェクトをダウンロードしてXcode上でビルドし、出来上がったFrameworkを~/Library/Frameworksフォルダに入れてから以下のAppleScriptを実行してください。

AppleScript名:ANSI,ASCII ARTを読み込んで画像にレンダリングする
– Created 2017-07-22 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AnsiLove" –https://github.com/ansilove/AnsiLove.framework
–http://piyocast.com/as/archives/4744

set aFol to choose folder

tell application "Finder"
  tell folder aFol
    set aList to every file as alias list
  end tell
end tell

repeat with i in aList
  set j to POSIX path of i
  
set newPath to retUUIDfilePath(j, "") of me
  
generatePNGFromAnsiData(j, newPath) of me
end repeat

on generatePNGFromAnsiData(aFile, outFile)
  set ansiGen to current application’s ALAnsiGenerator’s new()
  
ansiGen’s renderAnsiFile:aFile outputFile:outFile |font|:"80×25" bits:"8" iceColors:false columns:"80" retina:false
end generatePNGFromAnsiData

–指定ファイルパスと同一階層にファイル名をUUID、拡張子を指定したものを作成して返す
on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (current application’s NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((current application’s NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

★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/23 指定の画像をASCII ART化してTextEditでオープン v3

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

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

asciiart_gc1_resized.png

画像種別をとくに問わないように対応してみました。ただし、PNG画像については背景が透過している場合には画像余白トリミングフレームワーク「KGPixelBoundsClipKit」を用いてトリミングし、JPEG画像に変換します。

最大の変更点は、TextEditに対して命令を投げてフォントを指定していたものから、NSMutableAttributedStringに対してCocoaの機能を利用してフォント種別を指定するように変更したところです(意外と翻訳元となるObjective-Cの記述例が見つからない)。

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

–> Download Framework Binary

AppleScript名:指定の画像をASCII ART化してTextEditでオープン v3
– Created 2017-07-23 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/4740

set targFontName to “Osaka-Mono” –”Courier New”

set aFile to choose file of type {“public.image”}
tell application “System Events”
  set aType to type identifier of aFile –get UTI
end tell

if aType is equal to “public.png” then
  –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
  
set aaFile to (POSIX file (savePath as string)) as alias
  
else if aType is equal to “public.jpeg” then
  –JPEGの場合
  
copy aFile to aaFile
  
else
  –PNGとJPEG以外の場合には読み込んでJPEGに変換
  
set aImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:(POSIX path of aFile)
  
set fRes to retUUIDfilePath(POSIX path of aFile, “jpg”) of me
  
set sRes to saveNSImageAtPathAsJPG(aImage, fRes, 100) of me
  
set aaFile to (POSIX file (fRes as string)) as alias
  
end if

–ASCII ARTに変換してHTMLとして出力
set htmlRes to jpegToAsciiArt(aaFile, 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 NSMutableAttributedString’s alloc()’s initWithData:(htmlData’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)) options:optDict documentAttributes:(missing value) |error|:(missing value)

–フォント種別およびサイズを指定してみた
set aRange to current application’s NSMakeRange(0, aStyledStr’s |length|())
set aVal1 to current application’s NSFont’s fontWithName:targFontName |size|:11
aStyledStr’s beginEditing()
aStyledStr’s addAttribute:(current application’s NSFontAttributeName) value:aVal1 range:aRange
aStyledStr’s endEditing()

–スタイル付きテキストを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”

–Macの画面(メインスクリーン)の高さを取得する
set dRec to ((current application’s NSScreen’s mainScreen())’s deviceDescription()’s NSDeviceSize) as record
set dHeight to (height of dRec) as integer

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

–指定ファイルパスと同一階層にファイル名をUUID、拡張子を指定したものを作成して返す
on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (current application’s NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((current application’s NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

–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)
  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
  
  
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/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/21 指定のJPEG画像をASCII ART(カラーhtml)化してクリップボードに転送

jp2aを利用して指定のJPEG画像を色付きHTML形式のアスキーアート化して出力し、HTMLをスタイル付きテキストに変換してクリップボードに入れるAppleScriptです。

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

Terminal.app上で、

  sudo brew install jp2a

でインストールできます。

HTML形式で出力したASCII ARTをスタイル付きテキストに変換するあたりにCocoaの機能(NSAttributedString)を用いています。

実行するとアスキーアート化する対象のJPEG画像を選択。クリップボードに書式付きテキストの状態で入るため、書式付きテキスト対応のエディタ(テキストエディットなど)にペーストしてください。

asciiart1_resized.png

asciiart2_resized.png

AppleScript名:指定のJPEG画像をASCII ART(カラーhtml)化してクリップボードに転送
– Created 2017-07-21 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/4733

set aFile to choose file of type {“public.jpeg”}
set htmlRes to jpegToAsciiArt(aFile, 100) of me
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)

set attrList to {aStyledStr}
restoreClipboard(attrList) of me

–与えられたファイルパスの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

–set the clipboard to….
on restoreClipboard(theArray as list)
  set thePasteboard to current application’s NSPasteboard’s generalPasteboard()
  
thePasteboard’s clearContents()
  
thePasteboard’s writeObjects:theArray
end restoreClipboard

★Click Here to Open This Script 

2017/07/19 CotEditor開発者の1024jpさんをTMUG例会にお呼びします

毎月第2土曜日、目黒で例会を行なっている「東京Macintosh Users Group」(以下、TMUG)の8月度の例会(8/12)にCotEditor開発者の1024jpさんをお呼びして、CotEditorについてお話しを聞く機会をご用意しました。

日時:8月12日(土)14:00〜17:00(14時開場、14:30開始)
場所:目黒区民センター内中小企業センター2階会議室
定員:48名

一般の方でも、TMUGホームページの登録フォームからお申し込みいただければ無料で参加していただけます。途中、ロボットよけのために、

 合い言葉:ローマ字の小文字で「川」と入れてください

とありますが、ここは「kawa」と入力してください。

CotEditorはオープンソースで開発が行われており、かつメインの開発者が入れ替わって続いてきたという珍しいプロジェクトでもあります。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

★Click Here to Open This Script 

2017/07/17 SQLiteをAppleScriptから呼び出すのに便利なSQLite Libが公開される

SQLiteをAppleScriptから呼び出しやすくする「SQLite Lib」AppleScript LibrariesをShane Stanleyが公開しました。

元になっているのは、FMDBフレームワーク。Objective-CによるSQLiteのラッパーです。このFMDBにAppleScriptとの値の受け渡しに便利な(主に、受け取ったあとのデータのParse処理)メソッドを追加した「FMDBAS」フレームワークというものがShane Stanleyによって開発されました。

そこからさらに進化して、FMDBASフレームワークをラッピングした「SQLite Lib」になりました。

Scripterの間では「do shell script」コマンド経由でSQLiteを呼び出す処理が、全世界的に使われていますが、do shell scriptによるSQLite呼び出しだと、テキストとして返ってくる処理結果をparseしてあげる必要があり、そこからさらにフィールドごとにAppleScriptのデータタイプに合わせてcastしてあげたりすると、かなりの手間になります。

このあたりをObjective-Cで記述してスピードアップさせ、AppleScript Libraries化することで(AppleScriptObjCを覚えることなく)SQLiteを用いたプログラムを書けるというのが本ライブラリの真骨頂です。

もちろん、Cocoaを呼び出すAppleScriptObjCを書くことができれば、高速かつ大量のデータを処理することができ、その大量のデータをSQLite DBに格納したり呼び出したりすることで、搭載メモリー(RAM)を大幅に超えるサイズのデータをスピーディーに扱えます。

Shane Stanleyによれば、do shell scriptでSQLiteを呼び出して処理するよりも、50〜100倍程度高速、とのこと。

sqlitelib_graph_resized.png

ただし、ファーストリリースのためいくつかまだこなれていない点もあります。

意外と基礎的なメソッドはガラ空き状態で、任意のSQLiteのDBファイルにいくつテーブルが存在するとか、テーブル中のスキーマ定義の状態がどーなっているかを調べるといった、基礎的かつ必要と思われる機能は用意されていません。

Terminal上からSQLiteのコマンドを叩きつつ調べることになります。このあたり、Script Editor上だけで対話的に調べられたらそのほうがよさそうな感じもします。

次に、既存のSQL文をSQLite Lib向けに書き換えるあたりでつまづきやすいです。既存のSQLを書き換えることを考えるよりも、新規に別のものを作るようなときに利用することを考えたほうがよいでしょう。実際、日本語Word.netのシソーラス辞書のSQLite DBをキーワード検索するSQL文を、SQLite Lib向けに書き換えるのは自分にはできませんでした(do shell scriptコマンドのまま使用中)。

→ 休みの日に1日、いろいろ試してみたらできました

既存のSQLをどのように書き換えるのか、そのあたりでもう少し資料が必要に思えます。

2017/07/16 ASOCでcolor popup buttonを作成

動的にWindow+View+popup buttonを作成し、色選択を行うpopup menuを作成するAppleScriptです。

color_popup.png

Script Editor上でControl-Command-Rと操作することで実行できます。アプレットとして保存して実行してもOKです。

color_popup2.png

いくつか候補がある色のうちからどれかを選ぶといった処理は割とあるのですが、色のプレビューを行うインタフェースはAppleScriptのデフォルトのコマンドでは用意されていないので、ちょっと作ってみました。

たとえば、テキストエディット上で資料の文章をチェックを行なっているような場合、見直す必要のある箇所に赤く色をつけておいて、赤くマークした箇所のみAppleScriptで抽出するとかいう処理は便利にやっています(ものすごく効率がいい)。赤以外でもマークした箇所を抽出するとかいったら、本Scriptのような部品を利用して「どの色でマークした箇所を抽出しようか」文章中の色付き部分を全部スキャンしたうえで色選択という処理ができるわけです。

Script EditorおよびASObjC Explorer 4上での実行は確認できていますが、Script Debugger 6.0.5 試用版ではクラッシュします。

1枚のみのWindowを作成する場合にはNSWindowControllerを用いる必要はないようですが、ためしにNSWindowControllerを省いたScriptも作って試してみたところ・・・安定性がやや損なわれるようでした。結局、NSWindowControllerがあったほうがよいのでは???

指定色でNSImageを作成してmenu itemに設定していますが、macOS 10.13ではこれが正方形でないとおもいどおりのサイズでメニューに表示されませんでした。

color_popup1013.png
▲本ScriptをmacOS 10.13で動かしたときのイメージ(10.12上で再現)

color_popup1013b.png
▲macOS 10.13上のpopup menu上で大きくColor部分を表示するときには、imageの縦横サイズを同じに?

→ macOS 10.13.0のバグの影響を受けていたことが判明。heightとwidthの記述順を入れ替えると想定どおりの動作をします

AppleScript名:ASOCでcolor popup buttonを作成
– Created 2017-07-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “Carbon” – AEInteractWithUser() is in Carbon
–http://piyocast.com/as/archives/4724

property windisp : false
property wController : false –いらなかったかも?

if current application’s AEInteractWithUser(-1, missing value, missing value) is not equal to 0 then return

set ap1List to {{65535, 0, 65535}, {0, 32896, 16448}, {0, 32896, 65535}, {19702, 31223, 40505}}

set aButtonMSG to “OK”
set aSliderValMSG to “Select Color”
set aVal to getPopupValues(ap1List, 65535, aButtonMSG, aSliderValMSG, 20) of me

on getPopupValues(ap1List, aColMax, aButtonMSG, aSliderValMSG, timeOutSecs)
  
  
set (my windisp) to true
  
  
set aView to current application’s NSView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, 360, 100))
  
  
–Labelをつくる
  
set a1TF to current application’s NSTextField’s alloc()’s initWithFrame:(current application’s NSMakeRect(30, 60, 80, 20))
  
a1TF’s setEditable:false
  
a1TF’s setStringValue:“Color:”
  
a1TF’s setDrawsBackground:false
  
a1TF’s setBordered:false
  
  
–Ppopup Buttonをつくる
  
set a1Button to current application’s NSPopUpButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(80, 60, 200, 20)) pullsDown:false
  
a1Button’s removeAllItems()
  
  
set a1Menu to current application’s NSMenu’s alloc()’s init()
  
  
set iCount to 0
  
repeat with i in ap1List
    copy i to {r1, g1, b1}
    
    
set nsCol to makeNSColorFromRGBAval(r1, g1, b1, aColMax, aColMax) of me
    
set anImage to makeNSImageWithFilledWithColor(64, 16, nsCol) of me
    
    
set aTitle to “col_test_” & (iCount as string)
    
set aMenuItem to (current application’s NSMenuItem’s alloc()’s initWithTitle:aTitle action:“actionHandler:” keyEquivalent:“”)
    (
aMenuItem’s setImage:anImage)
    (
aMenuItem’s setEnabled:true)
    (
a1Menu’s addItem:aMenuItem)
    
    
set iCount to iCount + 1
  end repeat
  
  
a1Button’s setMenu:a1Menu
  
  
  
–Buttonをつくる
  
set bButton to (current application’s NSButton’s alloc()’s initWithFrame:(current application’s NSMakeRect(80, 10, 140, 40)))
  
bButton’s setButtonType:(current application’s NSMomentaryLightButton)
  
bButton’s setBezelStyle:(current application’s NSRoundedBezelStyle)
  
bButton’s setTitle:aButtonMSG
  
bButton’s setTarget:me
  
bButton’s setAction:(“clicked:”)
  
bButton’s setKeyEquivalent:(return)
  
  
aView’s addSubview:a1TF
  
  
aView’s addSubview:a1Button
  
aView’s addSubview:bButton
  
aView’s setNeedsDisplay:true
  
  
–NSWindowControllerを作ってみた(いらない?)
  
set aWin to (my makeWinWithView(aView, 300, 100, aSliderValMSG))
  
  
set wController to current application’s NSWindowController’s alloc()
  
wController’s initWithWindow:aWin
  
  
wController’s showWindow:me
  
  
set aCount to timeOutSecs * 100
  
  
set hitF to false
  
repeat aCount times
    if (my windisp) = false then
      set hitF to true
      
exit repeat
    end if
    
delay 0.01
    
set aCount to aCount - 1
  end repeat
  
  
my closeWin:aWin
  
  
if hitF = true then
    set s1Val to a1Button’s titleOfSelectedItem() as string
  else
    set s1Val to false
  end if
  
  
return s1Val
  
end getPopupValues

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

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

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

–Popup Action Handler
on actionHandler:sender
  set aTag to tag of sender as integer
  
set aTitle to title of sender as string
end actionHandler:

on makeNSColorFromRGBAval(redValue as integer, greenValue as integer, blueValue as integer, alphaValue as integer, aMaxVal as integer)
  set aRedCocoa to (redValue / aMaxVal) as real
  
set aGreenCocoa to (greenValue / aMaxVal) as real
  
set aBlueCocoa to (blueValue / aMaxVal) as real
  
set aAlphaCocoa to (alphaValue / aMaxVal) as real
  
set aColor to current application’s NSColor’s colorWithCalibratedRed:aRedCocoa green:aGreenCocoa blue:aBlueCocoa alpha:aAlphaCocoa
  
return aColor
end makeNSColorFromRGBAval

–指定サイズの画像を作成し、指定色で塗ってファイル書き出し
on makeNSImageWithFilledWithColor(aWidth, aHeight, fillColor)
  set anImage to current application’s NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight))
  
anImage’s lockFocus()
  

  
set theRect to {{x:0, y:0}, {height:aHeight, width:aWidth}}
  
set theNSBezierPath to current application’s NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  

  
fillColor’s |set|() –色設定
  
theNSBezierPath’s fill() –ぬりつぶし
  

  
anImage’s unlockFocus()
  

  
return anImage
end makeNSImageWithFilledWithColor

★Click Here to Open This Script 

2017/07/14 macOS 10.13=AppleScript 2.7

一般公開されたmac OS 10.13 Public Betaを見るかぎり、AppleScriptの処理系がバージョンアップされてバージョン2.7になっています。

macOS Version AppleScript JXA
10.10 2.4 1.0
10.11 2.5 1.1
10.12 2.5 1.1
10.13 2.7 1.1

 「おいおい、バージョン2.6はどこに行ったんだ?」(10.12ではバージョン2.5)

というところですが、詳細は不明です。それとなく「リリースノート出す気あんの?」と聞いてみたらその意向はあるようです。このあたりは、オフィシャルで公開情報が出てくるまで待つしかありません。

macOS 10.12ではmacOS自体のセキュリティ系の機能とAppleScriptバージョン2.5の処理系の整合性が取れていなかった(とくに、droplet処理時)ので、このあたりの整合性を取り戻すのが(Apple的な)急務ということになるでしょう。

また、新規に導入されたファイルシステム「APFS」「戦場の絆」プレイヤー的にはAPFSDSと見間違える)の影響もあるので、そのあたりの「穴埋め」も行っていることでしょう(ファイルシステム上に存在しない情報を取得する命令については、ヌル文字列を返してくる)。

macOS 10.12まではファイル情報を取得するinfo for命令でfile creatorとfile typeの情報を取得できていました(廃止されたとか言われていたのに)が、macOS 10.13+APFSではそもそもそんな管理項目が存在しないので、何も返ってこなくなります。

このあたり、ファイルの種別判定部分はmacOS 10.13+AppleScript 2.7+APFSでは書き換えが必要になるはずです。事実、Piyomaru Script Assistantでそういうパーツが出てきています。

APFSの仕様ではタイムスタンプがナノ秒単位になっていますが、Finder経由でタイムスタンプを取得すると結果はAppleScriptのdate型。FinderやSystem Events経由では秒以下の情報を取得できないことを確認しています。Cocoa経由で取得するにしても、どのように扱うことになるのか興味のあるところです。

Cocoaの呼び出し関連では、Blocks構文を要求するAPIをバージョン2.5までのAppleScriptでは呼べていないので、これがものすごくフラストレーション源になっています。あと、ASOCを書いていて腹が立つのは、

  「精度指定の数値をパラメータに要求するようなAPIが呼べない」

ことです。このあたり、なんとかならないものなのか。あと、AppleScript自体の数値(number)型の有効桁数が少なすぎて変数に入れられないEnumとかあるので、number型の有効桁数を拡張するとか、倍精度(有効桁数の多い)の数値型を新設するとかしてほしいところです。

また、Swiftで書かれたFrameworkの呼び出しが、自分はできていないので(ProjectにBridgingHeaderを追加すれば呼べるといった声もありますが)、このあたりなんとかならないものかと思っています。

sayコマンドで日本語音声を指定して特定の文字列を読ませても正しく読み上げが行われないバグは直っていません。バグレポートを書いてテスト用のコードまでAppleに提出していますが、「もげる」「捥げる」という文字を読ませると不完全な結果しか返ってきません。iOS上でも同様です。ASのsayコマンドではなくCocoaのレベルで問題が出ているようですが、これが音声データそのものに問題があるとしたら、音声データのライセンス提供元との調整になるはず。

ほかには、Script Menuから呼び出すAppleScriptに対して制限がきつすぎる(書類のオープン/クローズなどを行うと処理を止められる)点についてバグレポートしていますが、このあたりはScript Editorのアップデートが行われていないため不明です。

スクリプトメニューからのAppleScript実行処理自体は/usr/bin/osascriptコマンドで行なっているので/usr/bin/osascript次第?

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 

2017/07/05 Numbersでアクセス解析データを合計 v3

Numbersで表示している最前面の書類のすべてのシート上にある表のデータを集計するAppleScriptです。

4年分ぐらいのBlogのアクセス集計を行うことになり、ページ単位のアクセス情報をNumbers上にペースト。

numbers1.png

月ごとにシートを分けて保存し、「アクセスURL」「アクセス回数」のデータを分析することにしました。データは月ごとにシートに分けているけれども、分析を行う際にはすべてをまとめる必要がある、というのと、Numbersにそんな便利な機能はないというのがミソ。こういう用途こそAppleScriptで自動化すべきものです。

アクセス解析ページのデータをNumbersにコピペで貼り付けて、集計自体はAppleScriptで実行。

Numbersからデータを取得する部分は割とさくっとできたのですが、問題は集計部分。

Cocoaの機能を使わないと処理が大変なことはわかっていたので、とりあえずNSCountedSetにページのURLを突っ込んで集計を試みましたが、NSCountedSetを他のデータから作るのは割と融通がききませんでした。

“A”が10回存在するというデータをNSCountedSetに突っ込もうとすると、

  {”A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”, “A”}

という配列を作って追加しないといけないようだったので、1,000回だったら1,000個のデータを含む配列を作って……とやっていたら、要素数が100万個ぐらいの巨大な配列を作ることになり、処理時間が余計にかかってしまいました(逆に、メモリ8Gバイトのマシンでよく動いたものだと)。

結局、このバージョンのように集計時には1つの巨大なレコードにページのURLとアクセス回数を記録するように変更しました。

   {”/path/to/url1″:1024, “/path/to/url2″:311…….}

このあたり、AppleScriptのレコードではURLそのものをラベルにすることは(許容されない特殊文字を含んでいるため)不可能ですが、CocoaのNSDictionary(正確にはNSMutableDictionary)であればこのようなラベルを持てるので、便利に使っています。

集計後にURLと回数を個別に取り出して配列に入れ、回数で降順ソート。

  {{accessCount:300, aURL:”/path/to/url1″}, {accessCount:200, aURL:”/path/to/url2″}}

NSCountedSetで集計していたときには80秒ぐらいかかっていましたが、NSDictionaryで集計したら7秒程度(MacBook Pro Retina 2012)で終了しました。

集計元のNumbers書類(34シート、各シートに表1つ。表の行数はだいたい1,000行ぐらい)から集計対象データを取り出す部分は4秒ぐらいなので、集計部分は3秒程度かかっています。

このAppleScript自体はそれほど汎用性があるわけではありませんが、Numbersに入れたデータをAppleScriptから集計するのも、Cocoaの機能を利用すれば割と問題なく行えるという「見本」であります。

AppleScript名:Numbersでアクセス解析データを合計 v3
– Created 2017-07-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4716

script spd
  property allData : {}
end script

–初期化
set (allData of spd) to {}

–Numbers書類上の各シートの表の所定の範囲からデータを取り出す
tell application “Numbers”
  set dCount to count every document
  
if dCount = 0 then return
  
  
tell front document
    set sCount to count every sheet
    
repeat with i from 1 to sCount
      tell sheet i
        tell table 1
          set aRowCount to row count
          
set aRange to range (“A2:B” & (aRowCount as string))
          
set aDat to value of every cell of aRange
          
set (allData of spd) to (allData of spd) & aDat
        end tell
      end tell
    end repeat
  end tell
end tell

–取り出したデータを集計(1つの巨大なRecordに動的に項目を作成しつつ集計)
set aLen to length of (allData of spd)
set aDic to current application’s NSMutableDictionary’s alloc()’s init()

repeat with i from 1 to aLen by 2
  set aKey to contents of item i of (allData of spd)
  
set newVal to contents of item (i + 1) of (allData of spd)
  
  
—解析先ディレクトリをしぼりこみ
  
if aKey begins with “/as/archives/” then
    set aValue to (aDic’s valueForKey:aKey)
    
if aValue = missing value then
      (aDic’s setObject:newVal forKey:aKey) –新規エントリ作成
    else
      (aDic’s setObject:(newVal + aValue) forKey:aKey) –加算
    end if
  end if
end repeat

–集計したデータを個別要素に分離し、アクセス回数でソート
set anArray to current application’s NSMutableArray’s alloc()’s init()
set allKeys to (aDic’s allKeys()) as list

repeat with i in allKeys
  set bVal to (aDic’s valueForKey:i)
  
set bDic to {accessCount:(bVal as integer), aURL:i}
  (
anArray’s addObject:bDic)
end repeat

set bList to sortRecListByLabel(anArray, “accessCount”, false) of me –降順ソート
return bList as list

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList, aLabelStr as string, ascendF as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
  
return sortedArray
end sortRecListByLabel

★Click Here to Open This Script 

2017/07/04 Mac App Storeでアプリケーション「Double PDF」を販売開始

Mac App StoreでMac用アプリケーション「Double PDF」(1,080円)の販売を開始しました。すべてAppleScriptで書いたアプリケーションです。

dpdf1.png

文字主体のPDF(電子ブックなど)やKeynoteなどのオフィス系アプリケーションで作成したPDFの差分チェック用ツールです。2つのPDFを同時にめくってチェックを行うのが基本的な機能で、差分を検出するために便利な機能が備わっています。

さて、なぜこのようなアプリケーションを作ったかという経緯についてご説明を。

同じデータ(Pages書類とかMarkdown書類とか)からPDFを作成するといっても、OSやアプリケーションのバージョンが変わると微妙に生成されるPDFは変わってきます(本当)。文字詰め、行間スペース、レイアウト順など、こまかいところは実に頻繁に変わります。

また、元データ自体に修正することがあるので、きちんと反映されているのか、修正点がよそに反映しないかどうかもPDF出力して、オリジナルと比較して確認したいところです。

こういう用途で真っ先に試してみるのは、Adobe Acrobat。PDFバージョン比較機能があるので試してみたところ、500ページぐらいのPDF本に対しては「微細な変化を検出しすぎる」こともあって、実務ではとても使えませんでした。とくに、不可視データの変化を検出しまくるので、Adobe Acrobatをこの用途に使うのはほぼ無理という判断に。

そこで、前のバージョンのOSで生成した本と現在のOS上で生成した本で変化が生じていないかをチェックするAppleScriptを書き、GUIまで作ってひととおり機能評価。SSDのマシンで動かした感じでは(MacBook Pro、MacBook Air)かなりいい印象でした。

dpdf0.png
▲開発最初期バージョンの画面。ここから周囲のみなさまのダメ出しによりいろいろ試行錯誤を

PDFを画像として評価して差分をチェックするのはGPUImage.frameworkで行なっています。GPUの強力な機能がAppleScriptからでも手軽に呼び出せています。

PDF本文テキストの差分を表示するのは、外部アプリケーション「BBEdit」「TextWrangler」。Mac App Storeに提出する際のチェックで引っかかって外しましたが、初期版ではFileMergeやVimdiffなどを操作してテキストの差分を表示する機能もありました(削ってしまいましたが)。

2text_diff_by_external_app_j_resized.png
▲外部アプリケーション(BBEdit)による文字差分のブラウズ

2text_diff_by_external_app_2_resized.png
▲途中のバージョンにあった、Vimdiffによる文字差分のブラウズ(廃止)

Adobe Acrobatよりも軽快に動作し、差分チェックを「ソートした本文文字」(PDF出力時にレイアウト順がよく変わるので)、「本文文字そのまま」「プレビュー画像としてチェック」などの方式をとりまぜてさまざまなケースに対応できるようにしてみました。

よろしくお買い求めください!(告知が続いたのはたまたま、、、)