Archive for the 'ファイル処理(file)' Category

2017/10/13 指定クラスがどのFrameworkに所属しているか検索

文字列として与えたCocoaのClassがどのFrameworkに所属しているかを検索するAppleScriptです。Spotlight検索のためにShane Stanleyの「Metadata Lib」のインストールを必要とします。

割と切実に欲しかったAppleScriptです。よくShane Stanleyから「そのクラス呼ぶんだったら、このFrameworkをuseで宣言しとかないとダメだぞー」と小突かれているので、「自動でチェックしてえなぁ(涙)」と思っていたところでした。

use文によるFrameworkの使用宣言は、AppleScriptそれ自体で宣言していなくても、Script Editor側で使用宣言しているとそのまま動いてしまったりするパターンがあるため、割と見過ごしてしまう例がありました(Script Editor上でエラーメッセージ出ないし)。

わざわざAppleのWeb Referenceを検索したりと、本質的でない不毛な作業が必要。

そこで、Class名を文字列で与えるとどのFrameworkをuseしないといけないのかを調べる本AppleScriptを書いたわけです。

ながらく「書きたい」と思っていたわけですが、どこを手がかりにすればよいか長らくわかっていませんでした。

それが、macOS 10.13.0のScripting Bridgeのバグに直面して、Scripting Bridgeの「仕組み」そのものについて自分でも調べる機会がありました(正確に把握するため、どこがダメでどこでAppleがミスしたのか追調査)。

すると、macOS内の/System/Library/Frameworksフォルダ内にあるApple純正フレームワークの中に「(フレームワーク名).bridgesupport」という記述ファイルが存在しており、この中にScripting Bridge経由で機能を公開する内容が書いてあることが見て取れました。

macOS 10.13.0では、この.bridgesupportファイルの記述が間違っていた、というのがShane Stanleyの指摘です。

あれ??? この.bridgesupportファイルの中には当該フレームワークが他の言語に対して公開しているクラス名やメソッド名などの一覧が書かれているわけで、この中を検索すれば、目的のクラスを使うにはどのフレームワークをuseコマンドで参照するように宣言すればよいかわかる???? 

完全にもともとの用途とは違う使い道ですが、わかることはわかる(はず)。

冗談半分で書いてみたら、自分の目的を果たすことができました。1回の検索あたり0.7〜1.1秒程度です。結果をキャッシュするとか、検索するごとに毎回読むのをやめるとかすればもっと大幅に高速化はできるものと思われますが、そこまで気合いを入れる内容ではないのでこんな感じでしょうか。

ただし、例外で「CIColor」がみつかりません。ほかにも、この方法で見つけられないものがあるかもしれません。思いつきを形にしたぐらいの内容なので、ミスがあってもご容赦を。

AppleScript名:指定クラスがどのFrameworkに所属しているか検索
– Created 2017-10-13 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use mdLib : script "Metadata Lib" version "1.0.0"
–http://piyocast.com/as/archives/4894

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

set t1Res to searchInBridgeSupport("NSString") of me
–>  {"Foundation.framework"}

set t2Res to searchInBridgeSupport("CLLocation") of me
–>  {"MapKit.framework", "CoreLocation.framework"}

set t3Res to searchInBridgeSupport("NSRect") of me
–>  {"WebKit.framework", "Foundation.framework", "AppKit.framework", "ScreenSaver.framework", "Quartz.framework", "Quartz.framework", "Carbon.framework"}

on searchInBridgeSupport(aKeyword)
  set theFolder to "/System/Library/Frameworks/"
  
set theFiles to mdLib’s searchFolders:{theFolder} searchString:"kMDItemDisplayName ENDSWITH %@" searchArgs:{".bridgesupport"}
  
  
set keyList to {}
  
set the end of keyList to "<class name=’" & aKeyword & "’>"
  
set the end of keyList to aKeyword
  
  
repeat with ii in keyList
    set matchedList to {}
    
set targString to contents of ii
    
    
repeat with i in theFiles
      set j to contents of i
      
      
set aStr to (NSString’s stringWithContentsOfFile:j encoding:NSUTF8StringEncoding |error|:(missing value))
      
set aRange to (aStr’s rangeOfString:targString)
      
      
if aRange’s location() ≠ current application’s NSNotFound and (aRange’s location()) < 9.99999999E+8 then
        if j does not contain "PyObjC" then –ignore PyObjC’s bridge support
          set tmpStr to (current application’s NSString’s stringWithString:j)
          
set pathList to tmpStr’s pathComponents() as list
          
set pathRes to contents of item 5 of pathList
          
set the end of matchedList to pathRes
        end if
      end if
    end repeat
    
    
if length of matchedList > 0 then
      return matchedList
    end if
    
  end repeat
  
  
return {}
end searchInBridgeSupport

★Click Here to Open This Script 

2017/09/27 macOS 10.13であらたにサポートされたHEIF画像をJPEGに変換

macOS 10.13であらたにサポートされたHEIF形式の画像をJPEGに変換するAppleScriptです。

HEIF(High Efficiency Image Format)は高品質の画像を少ないファイル容量で保持できる画像フォーマット。macOS 10.13およびiOS 11からサポートされるようになりました。拡張子は「heif」あるいは「heic」とのこと。

実際にHEIFのサンプル画像をJPEGに圧縮率を変更しつつ書き出してみたところ、60%ぐらいの圧縮率のJPEGファイルとHEIF画像が同程度のファイルサイズ(素材によるんでしょうけれど)。非圧縮のJPEG(1.6Mバイト)に対してHEIFは340Kバイト。見た目は非圧縮のJPEGと区別できませんでした。

heif_preview.png

HEIF形式の画像ファイルをFinder上でダブルクリックすると、Preview.appでなにごともなく表示されます。ただし、JPEGなどの画像をHEIFで書き出す機能はPreview.appにはついておらず、他の環境でサポートしていないHEIF画像をそのまま外に出してほしくない、といったところでしょうか。

heif_preview2.png

とりあえず、Cocoaを経由してHEIF画像をAppleScriptでハンドリングできないかと調べてみたところ、普通に(何も変更なく)NSImageに読み込んで、JPEGで書き出せました。

その逆に、JPEG画像をHEIF形式に変換する方法は、調べてもあまり素直な方法(Cocoaのオブジェクトを呼び出して書き出し)は見つかりません。無理やりBrew経由でコマンドラインツールをインストールして変換という状況。

それもそのはずで、HEIFは画像フォーマットというよりもコンテナであり、その中にはさまざまなデータ(画像、派生画像、イメージシーケンス、補助画像アイテム、メタデータ)を格納できるようになっているとのこと。

一応、AVFoundationに画像形式「AVFileTypeHEIF」の定義があるようです。

macOS 10.14〜10.15ぐらいでOS内にきちんとした仕組みを用意しようというところなんでしょうか。

AppleScript名:指定HEIF画像をJPG形式で保存
– Created 2017-09-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.7 –10.13 or later
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4851

set aFile to POSIX path of (choose file of type {“public.heic”} with prompt “Select HEIF image file”)
set aImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:aFile

set fRes to retUUIDfilePath(aFile, “jpg”) of me
set sRes to saveNSImageAtPathAsJPG(aImage, fRes, 0.6) of me

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

★Click Here to Open This Script 

2017/09/25 書式つきテキストを組み立ててサイズを取得して画像書き出し v2

複数行にわたる書式付きテキストを組み立てて、描画サイズを取得して画像書き出しするAppleScriptです。

スタイル付きテキスト(NSAttributedString)を作成し、その描画エリアのサイズを取得。そんなもん、こんな手軽に求められるとは思ってもみませんでした。

描画エリアのサイズをもとに空白画像を作成して、そこにスタイル付きテキストを描画。結果の画像を指定パス名で書き出します。

test9999.png
▲本Scriptの実行結果(macOS 10.12)。出力用の文字列を長くしても、描画サイズを調べて画像化しているので、途中で途切れたりはしません

ただし、意外なところでOSバージョンによる挙動の差が出ました。書式付きテキストの描画サイズの取得ではなく、下地の塗りつぶし(白)についてです(下図参照)。

1013_strange_behavior.png
▲macOS 10.12(上)とmacOS 10.13GM(下)の本Scriptの実行結果。検証のために文字データを変更(途中の改行を減らしている)

macOS 10.13のRelease Build(Build 17A365)でも同様の結果になりました。macOS 10.10で実験したら10.12と同様の結果に。何か(トンでもない場所の)仕様が変わったのだろうか?

→ macOS 10.13のバグでした。対応版を掲載しています

AppleScript名:書式つきテキストを組み立ててサイズを取得して画像書き出し v2
– Created 2017-09-25 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/4843

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSBezierPath : a reference to current application’s NSBezierPath
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSPNGFileType : a reference to current application’s NSPNGFileType
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 NSFont : a reference to current application’s NSFont
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSImage : a reference to current application’s NSImage
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSString : a reference to current application’s NSString
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSColor : a reference to current application’s NSColor

set outPath to “~/Desktop/test9999.png”
set fillColor to NSColor’s whiteColor –塗り色
set aFontSize to 48
set aString to “Takaaki” & return & “Naganoya” & return & “長野谷” & return & “隆昌”

set anAssrStr to makeRTFfromParameters(aString, “HiraMinProN-W3″, aFontSize, -2, (aFontSize * 1.5)) of me
set aSize to anAssrStr’s |size|()
–>  {width:408.0, height:187.2}

set attrStrWidth to width of aSize
set attrStrHeight to height of aSize

set {xPos, yPos} to {0, 0}

–下地の画像を作成
set tmpImg1 to makeImageWithFilledColor(attrStrWidth, attrStrHeight, fillColor) of me

–下地の画像の上にAttributed Stringを描画
set tmpImg2 to drawAttributedStringsOnImage(tmpImg1, anAssrStr, xPos, yPos) of me

–PNG形式でファイルに保存
set aRes to saveImageRepAtPathAsPNG(tmpImg2, outPath) of me

–画像のうえに指定のスタイル付きテキストを描画して画像を返す
on drawAttributedStringsOnImage(anImage, anAssrStr, xPos, yPos)
  anImage’s lockFocus()
  
anAssrStr’s drawAtPoint:(current application’s NSMakePoint(xPos, yPos))
  
anImage’s unlockFocus()
  
return anImage
end drawAttributedStringsOnImage

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  –Font
  
set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
–Color
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
–Kerning
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
–Underline
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
–Ligature
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
–Paragraph space
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
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 makeImageWithFilledColor(aWidth, aHeight, fillColor)
  set anImage to 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 NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  
fillColor’s |set|()
  
theNSBezierPath’s fill()
  
anImage’s unlockFocus()
  
  
return anImage
end makeImageWithFilledColor

–画像を指定パスにPNG形式で保存
on saveImageRepAtPathAsPNG(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
end saveImageRepAtPathAsPNG

★Click Here to Open This Script 

2017/09/24 迷路をRTFで作成して脱出経路を赤く着色する v3

2D迷路の作成プログラム「MazeFinder」(By Toby Jennings)をフレームワーク化したものを呼び出して、文字ベースの迷路データをRTFに変換して脱出経路情報を着色してデスクトップに書き出すAppleScriptのアップデート版です。

迷路データの作成はこの際どうでもよく、文字列の検索を行う部分で問題が起こったことへの対処を行いました。

NSStringの文字検索メソッドrangeOfString: で、検索文字列が見つからなかった場合にNSNotFound(=-1)が返ってくるはずですが、macOS 10.13 betaで動かしてみたところNSNotFoundではなく巨大な数が返ってくることに気づきました。9,223,372,036,854,775,807と巨大すぎてAppleScriptの処理系では数値の精度上限を超えてしまっています。

いろいろ情報収集してみたところ、10.13にかぎらずよく知られた動作のようで、-1ではなく巨大な数を返してしまうパターンがある、と認識しておく必要があるようです(いつものApple仕事)。

AppleScript名:迷路をRTFで作成して脱出経路を赤く着色する v3
– Created 2017-09-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “MazeFinder” –https://github.com/tcjennings/MazeFinder
–http://piyocast.com/as/archives/4842

property NSColor : a reference to current application’s NSColor
property Board2D : a reference to current application’s Board2D
property NSUUID : a reference to current application’s NSUUID
property NSDictionary : a reference to current application’s NSDictionary
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSFont : a reference to current application’s NSFont
property NSString : a reference to current application’s NSString
property NSDocumentTypeDocumentAttribute : a reference to current application’s NSDocumentTypeDocumentAttribute
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property Pathfinder : a reference to current application’s Pathfinder
property NSLiteralSearch : a reference to current application’s NSLiteralSearch
–NSNotFoundはプロパティに代入しても認識されなかった

set targFontName to “Courier-Bold” –PostScript Name
set targFontSize to 13 –Point

–迷路テキストデータ作成→Attributed Stringに
set aStr to create2DMazeAndSolveIt(30, 30, 1, 1, 28, 28) of me –Plain Text
set anAttr to changeAttrStrsFontAttribute(aStr, targFontName, targFontSize) of me –Attributed String

–迷路データに着色(参照呼び出し, Call by reference)
markCharOfAttributedString(anAttr, aStr, “!”, (NSColor’s redColor())) of me

–結果のRTFをデスクトップ上に書き出す。ファイル名はUUID.rtf
set targFol to current application’s NSHomeDirectory()’s stringByAppendingPathComponent:“Desktop”
set aRes to my saveStyledTextAsRTF(targFol, anAttr)

–指定のAttributed String内で指定文字列が含まれる箇所に指定の色をつける(結果はメイン側に参照渡し)
on markCharOfAttributedString(anAttr, origStr, aTargStr, aColor)
  set rList to searchWordWithRange(origStr, aTargStr) of me
  
repeat with ii in rList
    (anAttr’s addAttribute:(NSForegroundColorAttributeName) value:aColor range:ii)
  end repeat
end markCharOfAttributedString

–指定の文字列をAttributed Stringに変換して任意のフォントを一括指定
on changeAttrStrsFontAttribute(aStr, aFontPSName, aFontSize)
  set tmpAttr to NSMutableAttributedString’s alloc()’s initWithString:aStr
  
set aRange to current application’s NSMakeRange(0, tmpAttr’s |length|())
  
set aVal1 to NSFont’s fontWithName:aFontPSName |size|:aFontSize
  
tmpAttr’s beginEditing()
  
tmpAttr’s addAttribute:(NSFontAttributeName) value:aVal1 range:aRange
  
tmpAttr’s endEditing()
  
return tmpAttr
end changeAttrStrsFontAttribute

–指定テキストデータ(atargText)内に、指定文字列(aSearchStr)が含まれる範囲情報(NSRange)をすべて取得する
on searchWordWithRange(aTargText, aSearchStr)
  set aStr to NSString’s stringWithString:aTargText
  
set bStr to NSString’s stringWithString:aSearchStr
  
set hitArray to NSMutableArray’s alloc()’s init()
  
set cNum to (aStr’s |length|()) as integer
  
  
set aRange to current application’s NSMakeRange(0, cNum)
  
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:NSLiteralSearch range:aRange
    
set aLoc to (detectedRange’s location)
    
–CAUTION !!!! Sometimes aLoc returns not NSNotFound (-1) but a Very large number
    
if (aLoc > 9.999999999E+9) or (aLoc = (current application’s NSNotFound)) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to aLoc as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
  end repeat
  
  
return hitArray
end searchWordWithRange

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(targFol, aStyledString)
  set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
set theName to (NSUUID’s UUID()’s UUIDString())
  
set thePath to 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

–2D迷路を作成して脱出経路を検索して文字列で迷路データを出力する
–迷路サイズ x,y スタート座標 x, y ゴール座標 x,y
on create2DMazeAndSolveIt(xMax, yMax, xStart, yStart, xGoal, yGoal)
  set myBoard to Board2D’s alloc()’s init()
  
myBoard’s setupBoardWithRows:xMax WithColumns:yMax –60×60ぐらいが上限。正方形でないとダメ
  
myBoard’s createMazeFromBoard()
  
  
set myPathfinder to Pathfinder’s alloc()’s init()
  
set myPath to myPathfinder’s findPathThroughMaze:myBoard fromX:xStart fromY:yStart toX:xGoal toY:yGoal
  
  
set aCount to myPath’s |count|()
  
if aCount > 0 then
    set aRes to myBoard’s drawPathThroughMaze:myPath
    
return aRes as string
  else
    return false
  end if
end create2DMazeAndSolveIt

★Click Here to Open This Script 

2017/09/23 指定日時以降に作成されたファイルをSpotlight検索 v3

指定フォルダ(Desktop)以下のファイルで、指定日時以降に作成されたものをSpotlight検索するAppleScriptの修正版です。

Metadata Libの作者であるShane Stanleyから直接「こうしたほうがいいよ」と教えてもらいました。たしかに、dateをイチイチISO8601日付文字列に変換しないといけないというのは、さすがにおかしいと思っていました(汗)。

記述と処理がシンプルになっただけで、v2でもv3でも処理時間に大差はありません。同じ対象に対して同じ検索を行なって、0.01秒弱というところでした。

本ルーチンでは指定年月日でNSDateオブジェクトを生成し、それをもとにMetadata LibでSpotlight検索を行なっています。実行のさいには、Metadata Libを~/Library/Script Libraries/フォルダに入れておく必要があります。

従来、自分はAppleのMailing ListやWebフォーラムなどの情報、および海外で販売されていたAppleScript CD-ROMなどを購入するなどして、ほぼ一方的に英語ベースの情報を受け身で収集していたわけですが、このようなBlogによる情報発信やらGoogle翻訳の精度向上の影響で、割と些細な情報も相互に伝わるようになりつつある昨今です。

AppleScript名:指定日時以降に作成されたファイルをSpotlight検索 v3
– Created 2017-09-21 by Takaaki Naganoya
– Modified 2017-09-22 by Shane Stanley
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4841

property NSTimeZone : a reference to current application’s NSTimeZone
property NSCalendar : a reference to current application’s NSCalendar

set aDate to getDateInternationalYMDhms(2017, 9, 21, 23, 2, 0) of me
–>  (NSDate) 2017-09-21 14:02:00 +0000

set thePath to POSIX path of (path to desktop)
–>  ”/Users/me/Desktop/”

set theFiles to mdLib’s searchFolders:{thePath} searchString:(“kMDItemFSCreationDate >= %@”) searchArgs:{aDate}
–> returns POSIX path list

on getDateInternationalYMDhms(aYear, aMonth, aDay, anHour, aMinute, aSecond)
  set theNSCalendar to NSCalendar’s currentCalendar()
  
set theDate to theNSCalendar’s dateWithEra:1 |year|:aYear |month|:aMonth |day|:aDay hour:anHour minute:aMinute |second|:aSecond nanosecond:0
  
return theDate
end getDateInternationalYMDhms

★Click Here to Open This Script 

2017/09/22 指定日時以降に作成されたファイルをSpotlight検索 v2

指定フォルダ(Desktop)以下のファイルで、指定日時以降に作成されたものをSpotlight検索するAppleScriptです。

本ルーチンの原型は、電子書籍を作成する際にMarkdown書類(MacDown)とPages書類をまとめてPDFに出力して1つのPDFに連結するAppleScriptで使っていたものです(こういう武器がないと、電子書籍を執筆からレイアウトから何から何までひとりで行うことなんてできません)。

bookfiles2.png
Pagesはともかく、MacDownからは指定フォルダにPDFを書き出すという機能は(MacDownのAppleScript用語辞書に機能が用意されていないため)AppleScriptからは利用できませんでした。そこで、GUI Scriptingで強引にデスクトップにPDF書き出しすることに。

デスクトップに書き出させたのは、保存ダイアログ上でCommand-Dのキーボードショートカットで一意に指定できる数少ないフォルダだからです(このあたり、生活の知恵)。

GUI Scriptingでファイル保存やファイル書き出しを行う場合、保存ダイアログがどこのフォルダを指し示すかはアプリケーション側まかせなので、保存/書き出し先フォルダをデスクトップに強引に指定する処理はたいへん重要です。GUI Scriptingはたいへんに弱点が多く不確実な手段ですが、不確実さを地道につぶしてあげることで、信頼性のある処理フローを構築できます。

PDF書き出し開始時のタイムスタンプを変数に記憶させておき、その時刻以降でデスクトップに生成されたPDFをすべて取得し、ファイル名でソート(ファイル名の先頭にソート用の仮想ノンブルをつけて管理)し、(末尾に生じていた意図しない空白ページ自動削除しつつ)1つのPDFに合成していました。

そのため、日時を指定して指定フォルダに存在するファイルをSpotlight検索する本ルーチンの重要度はとても高かったわけです。

当初は日付情報をJan 1 2001 00:00:00 GMTからの相対秒(Absolute Time)で指定していたのですが、OSのバージョンアップにともないこの書き方が使えなくなったため、仕方なく対処。

本ルーチンでは指定年月日でdateオブジェクトを生成したあと、ISO8601形式の日付文字列に変換し、それをもとにShane StanleyのMetadata LibでSpotlight検索を行なっています。実行のさいには、Metadata Libを~/Library/Script Libraries/フォルダに入れておく必要があります。

AppleScript名:指定日時以降に作成されたファイルをSpotlight検索 v2
– Created 2017-09-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4838

property NSTimeZone : a reference to current application’s NSTimeZone
property NSCalendar : a reference to current application’s NSCalendar
property NSDateFormatter : a reference to current application’s NSDateFormatter

set aDate to getDateInternationalYMDhms(2017, 9, 21, 23, 2, 0) of me
–>  date “2017年9月21日木曜日 23:02:00″

set bStr to retISO8601DateTimeString(aDate) as string
–>  ”2017-09-21T14:02:00Z”

set thePath to POSIX path of (path to desktop)

set theFiles to mdLib’s searchFolders:{thePath} searchString:(“kMDItemFSCreationDate >= ’$time.iso(\”" & bStr & “\”)’”) searchArgs:{}
–> returns POSIX path list

–NSDate -> ISO8601 Date & Time String
on retISO8601DateTimeString(targDate)
  set theNSDateFormatter to NSDateFormatter’s alloc()’s init()
  
theNSDateFormatter’s setDateFormat:“yyyy-MM-dd’T’HH:mm:ss’Z’”
  
theNSDateFormatter’s setTimeZone:(NSTimeZone’s alloc()’s initWithName:“UTC”)
  
return (theNSDateFormatter’s stringFromDate:targDate) as text
end retISO8601DateTimeString

–現在のカレンダーで指定年月のdate objectを返す(年、月、日、時、分、秒)
on getDateInternationalYMDhms(aYear, aMonth, aDay, anHour, aMinute, aSecond)
  set theNSCalendar to NSCalendar’s currentCalendar()
  
set theDate to theNSCalendar’s dateWithEra:1 |year|:aYear |month|:aMonth |day|:aDay hour:anHour minute:aMinute |second|:aSecond nanosecond:0
  
return theDate as date
end getDateInternationalYMDhms

★Click Here to Open This Script 

2017/09/20 迷路をRTFで作成して脱出経路を赤く着色する

2D迷路の作成プログラム「MazeFinder」(By Toby Jennings)をフレームワーク化したものを呼び出して、文字ベースの迷路データをRTFに変換して脱出経路情報を着色してデスクトップに書き出すAppleScriptです。

maze1.png

本Scriptを試す場合には、MazeFinder.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。

60×60の迷路を作成して着色してファイル書き出しを終了するまでに、開発環境で0.08秒程度かかりました。

–> Download Framework Binary

迷路作成の処理自体についてはとくに意味はありませんが、迷路プログラム自体にもいろいろあるようです。このMazeFinderについてはいろいろ制約条件がきつく(最大サイズ60×60程度、迷路が正方形でないとクラッシュ)、あまり迷路らしい迷路にもなっていません。

「テキストデータをRTFで出力して特定文字に着色する」というあたりの処理がミソでしょうか。

AppleScript名:迷路をRTFに作成して脱出経路を赤く着色する
– Created 2017-09-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “MazeFinder” –https://github.com/tcjennings/MazeFinder
–http://piyocast.com/as/archives/4831

property NSColor : a reference to current application’s NSColor
property Board2D : a reference to current application’s Board2D
property NSUUID : a reference to current application’s NSUUID
property NSDictionary : a reference to current application’s NSDictionary
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSFont : a reference to current application’s NSFont
property NSString : a reference to current application’s NSString
property NSDocumentTypeDocumentAttribute : a reference to current application’s NSDocumentTypeDocumentAttribute
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property Pathfinder : a reference to current application’s Pathfinder
property NSLiteralSearch : a reference to current application’s NSLiteralSearch

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

–Create Maze Text
set aStr to create2DMazeAndSolveIt(30, 30, 1, 1, 28, 28) of me
set anAttr to NSMutableAttributedString’s alloc()’s initWithString:aStr

set bList to {“!”} –Mark Target List

–Set Fixed Width Font
set aRange to current application’s NSMakeRange(0, anAttr’s |length|())
set aVal1 to NSFont’s fontWithName:targFontName |size|:13
anAttr’s beginEditing()
anAttr’s addAttribute:(current application’s NSFontAttributeName) value:aVal1 range:aRange
anAttr’s endEditing()

–Change Attribute (red color)
repeat with i in bList
  set rList to searchWordWithRange(aStr, i as string) of me
  
repeat with ii in rList
    (anAttr’s addAttribute:(current application’s NSForegroundColorAttributeName) value:(NSColor’s redColor()) range:ii)
  end repeat
end repeat

–結果のRTFをデスクトップ上に書き出す
set targFol to current application’s NSHomeDirectory()’s stringByAppendingPathComponent:“Desktop”
set aFileName to (NSUUID’s UUID()’s UUIDString() as text)
set aRes to my saveStyledTextAsRTF(aFileName, targFol, anAttr)

on searchWordWithRange(aTargText, aSearchStr)
  set aStr to NSString’s stringWithString:aTargText
  
set bStr to NSString’s stringWithString:aSearchStr
  
set hitArray to NSMutableArray’s alloc()’s init()
  
set cNum to (aStr’s |length|()) as integer
  
  
set aRange to current application’s NSMakeRange(0, cNum)
  
  
set aCount to 1
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:NSLiteralSearch range:aRange
    
if detectedRange’s location is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
    
set aCount to aCount + 1
  end repeat
  
  
return hitArray
end searchWordWithRange

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName, targFol, aStyledString)
  –Convert NSMutableStyledStrings to RTF
  
set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
– Build Path
  
set theName to NSString’s stringWithString:aFileName
  
–set theName to theName’s stringByReplacingOccurrencesOfString:”/” withString:”_”
  
–set theName to theName’s stringByReplacingOccurrencesOfString:”:” withString:”_”
  
set thePath to 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

–2D迷路を作成して脱出経路を検索して文字列で迷路データを出力する
–迷路サイズ x,y スタート座標 x, y ゴール座標 x,y
on create2DMazeAndSolveIt(xMax, yMax, xStart, yStart, xGoal, yGoal)
  set myBoard to Board2D’s alloc()’s init()
  
myBoard’s setupBoardWithRows:xMax WithColumns:yMax –60×60ぐらいが上限。正方形でないとダメ
  
myBoard’s createMazeFromBoard()
  
  
set myPathfinder to Pathfinder’s alloc()’s init()
  
set myPath to myPathfinder’s findPathThroughMaze:myBoard fromX:xStart fromY:yStart toX:xGoal toY:yGoal
  
  
set aCount to myPath’s |count|()
  
if aCount > 0 then
    set aRes to myBoard’s drawPathThroughMaze:myPath
    
return aRes as string
  else
    return false
  end if
end create2DMazeAndSolveIt

★Click Here to Open This Script 

2017/09/19 DDFileReaderによる行単位でのテキストファイル読み込みテスト

オープンソースのテキスト行単位読み込みプログラム「DDFileReader」(By Dave DeLong)をCocoa Frameworkにした「DDFileReader.framework」を呼び出してテキストの行単位読み込みを行うAppleScriptです。

AppleScriptの標準命令readコマンドでも行単位読み込みができるので(掲載サンプル参照)、本フレームワークを使う必要はほとんどないのですが、とりあえず実験してみました。

本Scriptを試す場合には、DDFileReader.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。オリジナルのままだとARC環境下でビルドできなかったので、releaseとかretainとかdeallocとかそのあたりのキーワードが含まれる行をコメントアウトして試していますが、その程度なので問題があったらぜひ知らせてください。自分でもコレを重要なプログラムで使うつもりはないのですが、問題があったら困ります。

–> Download Framework Binary

AppleScript名:DDFileReaderによる行単位でのテキストファイル読み込みテスト
– Created 2017-09-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “DDFileReader” –https://stackoverflow.com/questions/1044334/objective-c-reading-a-file-line-by-line
–http://piyocast.com/as/archives/4829

property DDFileReader : a reference to current application’s DDFileReader
property NSMutableArray : a reference to current application’s NSMutableArray

set tmpPath to POSIX path of (choose file)
set aReader to DDFileReader’s alloc()’s initWithFilePath:tmpPath

set tArray to NSMutableArray’s new()
repeat
  set aLine to aReader’s readLine()
  
if aLine = missing value then exit repeat
  
tArray’s addObject:aLine
end repeat

return length of (tArray as list)

★Click Here to Open This Script 

AppleScript名:Pure ASによるファイルの行単位読み込み
set aFile to choose file of type {“public.plain-text”}

set newList to {}

tell current application
  set fH to open for access aFile without write permission
  
try
    repeat
      set fRes to read fH as «class utf8» until return
      
log fRes
      
set the end of newList to fRes
    end repeat
  on error
    close access fH
  end try
  
end tell

return (length of newList)

★Click Here to Open This Script 

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

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

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

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

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

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

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

 System Events:1秒
 Finder:6秒

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

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

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

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

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

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

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

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

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

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

tui4.png

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

—————

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

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

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

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

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

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

★Click Here to Open This Script 

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

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

filenames2.png

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

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

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

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

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

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

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

load framework –BridgePlus’s force framework loading command

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

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

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

★Click Here to Open This Script 

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

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

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

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

text_download.png

ファイル名が、

filename_before.png

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

filename_after.png

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

file_contents.png

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

knight_magic4.png

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

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

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

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

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

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

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

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

on findPattern:thePattern inString:theString
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

★Click Here to Open This Script 

2017/08/31 指定フォルダ以下のすべてのファイルを再帰で取得 v2

指定フォルダ以下のすべてのファイルを再帰で取得するAppleScriptです。Finder経由ではなくNSFileManager経由で高速に処理します。

さすがにSpotlightの方が(実測値で70倍ぐらい)高速なので、Spotlightが効かない場所に対する非常用という位置づけです。

掲載リストのようにデスクトップフォルダ以下の、拡張子「.scpt」「.scptd」の書類のパスをPOSIX pathのlistで取得する処理を自分の環境で行なってみたところ、

  NSFileManager経由での再帰取得処理:1.5 sec.
  Spotlight経由でのファイル取得処理:0.02 sec.

という結果になりました(5回実行時の平均値)。いまどきFinderで再帰処理とかいうのは選択肢として「絶対にありえない」ので比較もしていませんが、同じ環境で同じ処理を行うとおそらく「数分」から「十数分」といったオーダーになるものと思います。

日常的にSpotlightによるファイル取得を行なっているので、本処理が活躍するケースはほとんどない見込みですが、ねんのため(Spotlightが効かない&メタデータが破損しているダメ環境でファイルを取得するための臨時処理)。

一緒に掲載しているSpotlight検索Scriptでは、Shane StanleyのMetadata Libを呼び出しています。実行のためには、Metadata Libを~/Libraries/Script Librariesフォルダにインストールしておく必要があります。

AppleScript名:指定フォルダ以下のすべてのファイルを再帰で取得 v2
– Created 2017-08-04 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/4796

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

–set aFol to POSIX path of (choose folder)
set aFol to POSIX path of (path to desktop folder)

–set aList to retFullPathWithinAFolderWithRecursive(aFol) of me
–set bList to retFullPathWithinAFolderWithRecursiveFilterByExt(aFol, “scpt”) of me
set cList to retFullPathWithinAFolderWithRecursiveFilterByExtList(aFol, {“scpt”, “scptd”}) of me
–set dList to retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, “png”, “スクリーン”) of me
–set eList to retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, {”scpt”, “scptd”}, “並列”) of me

–指定フォルダ以下のすべてのファイルを再帰で取得
on retFullPathWithinAFolderWithRecursive(aFol)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
return anArray as list
end retFullPathWithinAFolderWithRecursive

–指定フォルダ以下のすべてのファイルを再帰で取得(拡張子で絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExt(aFol, aExt)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension == [c]%@” argumentArray:{aExt}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExt

–指定フォルダ以下のすべてのファイルを再帰で取得(拡張子リストで絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtList(aFol, aExtList)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension IN [c]%@” argumentArray:{aExtList}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtList

–指定フォルダ以下のすべてのファイルを再帰で取得(文字列と拡張子で絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtListAndFileNameString(aFol, aExt, aNameString)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension == [c]%@ && lastPathComponent CONTAINS %@” argumentArray:{aExt, aNameString}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtListAndFileNameString

–指定フォルダ以下のすべてのファイルを再帰で取得(文字列と拡張子リストで絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, aExtList, aNameString)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension IN [c]%@ && lastPathComponent CONTAINS %@” argumentArray:{aExtList, aNameString}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString

★Click Here to Open This Script 

AppleScript名:指定フォルダ以下のすべてのファイルを再帰で取得(spotlightで処理).scpt
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4796

–set theFolder to choose folder
set theFolder to (path to desktop folder)
set theFiles to mdLib’s searchFolders:{theFolder} searchString:“kMDItemContentType IN[c] %@” searchArgs:{“com.apple.applescript.script”, “com.apple.applescript.script-bundle”}

★Click Here to Open This Script 

2017/08/31 指定フォルダ内の指定文字列を名称に含むファイルを抽出する v3

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

前バージョンのScriptに対してShane Stanleyからツッコミが入って、

(1)as «class furl»で結果を返す(fileのlistとして結果が返る)処理はmacOS 10.10.xでは結果が正しくBridgeされないので、もう少し丁寧に処理するか10.10を対象外に

(2)ループ処理について、AppleScriptで処理可能な常識的な範囲のアイテム数(高速化処理をしていなければ数千程度)の範囲であればrepeat with in のループ処理の方がobjectEnumeratorでループするより速い(数十万〜数百万アイテムの場合にはobjectEnumerator)

(3)Package File(実体はフォルダ)についても区別する/しない処理を行なったほうがいい

ということで修正してみました。指定フォルダから、指定文字列を名称に含むファイルを抽出する処理は、登場頻度もたいへん高いので、Finder経由ではなくこうしたCocoaの機能を利用するルーチンにさしかえると大幅な処理速度向上が見込まれます。とくに、非力なマシンでの速度向上が期待されます。

個人的には、macOS 10.10.xはScripting Bridge系のバグやFolder Actionのバグなど問題が多かったので、なるべく対象外にしたい気持ちでいっぱいです。

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

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

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

set file1aRes to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:false
set file1bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, false)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….”/Users/me/Desktop/スクリーンテスト.scptd”}

set file2Res to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:true
set file2bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, true)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….}

–Get files

–指定フォルダ内の指定文字列を含むファイル名のファイルをPOSIX pathのlistで抽出する
on filterOutFilesByName:(fileNameStr as string) fromDirectory:(sourceFolder) exceptPackages:(packageF as boolean)
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat with i in foundItemList
    set j to contents of i
    
set {theResult, isDirectory} to (j’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value))
    
    
–Collect files
    
if (isDirectory as boolean = false) then
      (anArray’s addObject:j)
      
    else if (packageF = false) then
      –Allow Package files?
      
set {theResult, isPackage} to (j’s getResourceValue:(reference) forKey:(current application’s NSURLIsPackageKey) |error|:(missing value))
      
if (isPackage as boolean) = true then
        (anArray’s addObject:j)
      end if
    end if
    
  end repeat
  
  
return (anArray’s valueForKey:“path”) as list
end filterOutFilesByName:fromDirectory:exceptPackages:

on filterOutFilesByNameFromDirectory(fileNameStr as string, fromDir, packageF as boolean)
  return my filterOutFilesByName:(fileNameStr as string) fromDirectory:fromDir exceptPackages:(packageF as boolean)
end filterOutFilesByNameFromDirectory

★Click Here to Open This Script 

2017/08/19 PDFの指定ページを削除 v4(複数ページ一括指定)

指定PDF書類のうちの指定ページをまとめて削除するAppleScriptです。

ページ指定にプラスの数値を指定すると絶対ページ数、マイナスの数値を指定するとページ末尾からの相対ページ数として解釈されます。ページ削除前に削除対象のページ数をすべて絶対ページに変換しつつ、重複分を削除し、削除対象ページを降順ソートします。

常識的な範囲内では、PDFからの指定ページ削除は行えるはずです。

ただ、この程度の実装だとすべてのPDFを対象にできないので困ります。Mac App Storeで販売中のアプリ「Double PDF」ではこのあたりの問題を解決したPDF処理ルーチンを仕込んであります。

AppleScript名:PDFの指定ページを削除 v4(複数ページ一括指定)
– Modified 2017-08-19 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
–http://piyocast.com/as/archives/4784

property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSArray : a reference to current application’s NSArray
property NSSet : a reference to current application’s NSSet
property |NSURL| : a reference to current application’s |NSURL|
property PDFDocument : a reference to current application’s PDFDocument

set inFile to (choose file of type {“pdf”} with prompt “Choose your PDF files:”)
set targPageList to {1, 3, 5, 7, -1, -2}

set pRes to removeSpecificPagesFromPDF(inFile, targPageList) of me

–指定PDF書類の複数ページの一括削除
on removeSpecificPagesFromPDF(inFileAlias, targPageNumList as list)
  set inNSURL to |NSURL|’s fileURLWithPath:(POSIX path of inFileAlias)
  
set theDoc to PDFDocument’s alloc()’s initWithURL:inNSURL
  
  
–削除対象ページリストをユニーク化して降順ソート(後方から削除)
  
set pRes to theDoc’s pageCount()
  
set t3List to relativeToAbsNumList(targPageNumList, pRes) of me
  
  
repeat with i in t3List
    copy i to targPageNum
    (
theDoc’s removePageAtIndex:(targPageNum - 1))
  end repeat
  
  
–Overwrite Exsiting PDF
  
set aRes to (theDoc’s writeToURL:inNSURL) as boolean
  
  
return aRes
end removeSpecificPagesFromPDF

–絶対ページと相対ページが混在した削除対象ページリストを絶対ページに変換して重複削除して降順ソート
on relativeToAbsNumList(aList, aMax)
  set newList to {}
  
  
repeat with i in aList
    set j to contents of i
    
if i < 0 then
      set j to aMax + j
    end if
    
    
if (j aMax) and (j is not equal to 0) then
      set the end of newList to j
    end if
  end repeat
  
  
set t1List to my uniquify1DList(newList, true)
  
set t2List to my sort1DNumList:t1List ascOrder:false
  
  
return t2List
end relativeToAbsNumList

on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

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

–Sort 1-Dimension List(String Number List)
on sort1DNumList:theList ascOrder:aBool
  tell NSSet to set theSet to setWithArray_(theList)
  
tell NSSortDescriptor to set theDescriptor to sortDescriptorWithKey_ascending_(“floatValue”, aBool)
  
set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor}
  
return (sortedList) as list
end sort1DNumList:ascOrder:

★Click Here to Open This Script 

2017/08/19 PDFの指定ページを削除 v3(PDFDocument経由でアクセス)

指定PDF書類のうちの指定ページを削除するAppleScriptです。

以前、PDFKit中の機能のうち指定ページ(PDFPage)を削除するものがPDFPageに見つからなかったので、新規PDFDocumentを作成して元PDFの削除対象「以外の」ページをコピーして上書き保存することで擬似的にページ削除を実現していました

ページ削除機能がないのはおかしいと考え、しつこく調べていたところ….PDFPageではなくPDFDocumetにページ削除の機能が存在することに気づきました。そのため、PDFPage経由で指定のページを削除してみたものです。

skim1.png
▲実行前

skim2.png
▲実行後(7ページ目を削除した)

AppleScript名:PDFの指定ページを削除 v3(PDFDocument経由でアクセス)
– Modified 2017-08-19 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
–http://piyocast.com/as/archives/4781

property |NSURL| : a reference to current application’s |NSURL|
property PDFDocument : a reference to current application’s PDFDocument

set inFile to (choose file of type {“pdf”} with prompt “Choose your PDF files:”)
set targPage to 7

set pRes to removeSpecificPageInPDF(inFile, targPage) of me

on removeSpecificPageInPDF(inFileAlias, targPageNum)
  set inNSURL to |NSURL|’s fileURLWithPath:(POSIX path of inFileAlias)
  
set theDoc to PDFDocument’s alloc()’s initWithURL:inNSURL
  
  
set pRes to theDoc’s pageCount()
  
if absNum(targPageNum) of me > pRes or targPageNum = 0 then
    error “PDF Page Range error. This PDF document has “ & (pRes as string) & ” pages. But you pointed “ & (targPageNum as string) & ” page from your script. “ & return & ” (available abs range :1…” & (pRes as string) & “, relative range: -1…-” & (pRes as string) & “)”
  end if
  
  
–Allow Relative Page Num ( -1 = the last page)
  
if targPageNum 0 then
    set targPageNum to pRes + targPageNum + 1
  end if
  
theDoc’s removePageAtIndex:(targPageNum - 1)
  
  
–Overwrite Exsiting PDF
  
set aRes to (theDoc’s writeToURL:inNSURL) as boolean
  
  
return aRes
end removeSpecificPageInPDF

on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

★Click Here to Open This Script 

2017/08/08 Metadata Lib 1.0

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

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

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

 someHander(aParamerer)

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

 someHander:aParameter

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

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

mdls系コマンド

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

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

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

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

mdfind系コマンド

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

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

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

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

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

どうなんでしょう?

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

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

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

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

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

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

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

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

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

load framework

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

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

★Click Here to Open This Script 

2017/07/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/06/16 指定PDFの最初のページに大量のスクウェアアノテーションを添付する

指定PDFの最初のページに大量のスクウェアアノテーションを添付するAppleScriptです。

他のGUIアプリケーションを併用せずQuartz Frameworkの機能を利用して、PDFに対するアノテーションの添付を行います。

square_anno1.png

PDFのアノテーションまわりはmacOS 10.13で大幅に変更されているため、本Scriptがそのまま10.13上でも動作することは期待していません。

AppleScript名:指定PDFの最初のページに大量のスクウェアアノテーションを添付する
– Created 2017-06-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “AppKit”
–http://piyocast.com/as/archives/4688

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “Select PDF”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()
set aPage to aPDFdoc’s pageAtIndex:0

set firstPage to (aPDFdoc’s pageAtIndex:0)

–Remove Annotation
my removeAnnotationFromPage:firstPage –Call by Reference

–Get PDF size by Point
set aBounds to aPage’s boundsForBox:(current application’s kPDFDisplayBoxMediaBox)
set aSize to |size| of aBounds

–Add Annotation
repeat with xNum from 30 to ((width of aSize) - 30) by 50
  repeat with yNum from 30 to ((height of aSize) - 30) by 50
    set squAnn to (current application’s PDFAnnotationSquare’s alloc()’s initWithBounds:{origin:{x:xNum, y:yNum}, |size|:{width:40, height:40}})
    (
squAnn’s setValue:(current application’s NSColor’s blueColor()) forAnnotationKey:(current application’s kPDFAnnotationKey_Color))
    (
squAnn’s setValue:(current application’s NSColor’s clearColor()) forAnnotationKey:(current application’s kPDFAnnotationKey_InteriorColor))
    (
firstPage’s addAnnotation:squAnn)
  end repeat
end repeat

–Save It
aPDFdoc’s writeToFile:aPOSIX

–Remove All Annotation from a Page. Call by Reference
on removeAnnotationFromPage:aPage
  set anoList to (aPage’s annotations()) as list
  
repeat with i in anoList
    (aPage’s removeAnnotation:i)
  end repeat
end removeAnnotationFromPage:

★Click Here to Open This Script 

2017/06/03 指定フォルダ中のPure ASのファイルを別フォルダに移動

指定フォルダ中のPure AppleScriptのファイルを別フォルダに移動するAppleScriptです。

最近は(処理速度が速いことや並列実行させやすいことから)なるべくCocoaの機能を呼び出すASOC(AppleScriptObjC)の部品を使うようにしているので、Pure AppleScript(Cocoaの機能を使わないもの)のファイルを整理して別フォルダに移動させておくためのScriptを書いてみました。

任意のAppleScript書類からソースを取得して、ソース中にuse framework “Foundation”の宣言文が入っていないかどうかを確認しています。入っていればASOC、入っていなければPure ASと判定。実行専用のScriptからはソースコードは取得できないので無視していますし、実行形式のアプレットについては最初の段階で対象外にしています。

AppleScript名:指定フォルダ中のPure ASのファイルを別フォルダに移動
– Created 2017-06-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OSAKit”
–http://piyocast.com/as/archives/4673

set tFol to choose folder with prompt “処理対象フォルダを選択”
set sFol to choose folder with prompt “Pure AppleScriptの移動先フォルダを選択”

tell application “Finder”
  tell folder (tFol)
    set sList to (every file whose name ends with “.scpt” or name ends with “.scptd”) as alias list
  end tell
end tell

repeat with i in sList
  set sRes to detectScriptIsPureASorASOC(i) of me
  
if sRes = false then
    tell application “Finder”
      move i to sFol
    end tell
  end if
end repeat

–指定AppleScriptファイルがPure ASかASOCかを判定して返す
on detectScriptIsPureASorASOC(aFile)
  set sText to getASsourceFor(aFile) of me
  
if sText = “” or sText = missing value then return missing value
  
if sText contains “use framework \”Foundation\”" then
    return true –ASOC (AppleScript with Cocoa Framework)
  else
    return false –Pure AppleScript
  end if
end detectScriptIsPureASorASOC

–指定AppleScriptファイルのソースコードを取得する(実行専用Scriptからは取得できない)
on getASsourceFor(anAlias as {alias, string})
  set anHFSpath to anAlias as string
  
set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of anHFSpath)
  
set theScript to current application’s OSAScript’s alloc()’s initWithContentsOfURL:aURL |error|:(missing value)
  
return theScript’s source() as text
end getASsourceFor

★Click Here to Open This Script 

2017/03/15 Spotlightで指定フォルダ以下の指定文字を含むファイル一覧を取得

Cocoaの機能を呼び出すAppleScriptObjCでは、さまざまなCocoaのAPIを呼び出す手法が模索されてきました。その中でもSpotlight検索は「割とまだ決定版の手法が確立していない」ものでありました。

spotlightでタグを指定して検索
http://piyocast.com/as/archives/3731

ASOCでmdfindするじっけん v4(フルパスを返す)
http://piyocast.com/as/archives/4122

などなど、徐々に進化してきて、Shane StanleyのAppleScript Libraries「BridgePlus」にSpotlightの呼び出し命令が実装されたりと、手段が洗練されてきたという経緯があります。

書籍「AppleScript 10大最新技術」にも掲載していますが、その時点のバージョンよりもはるかにこなれた書き方がML上でShane Stanleyから提示されました。いままでよりもはるかに短いので、ちょっと腰を抜かしてしまったほど。しかも、とてもさりげなく、、、

「ああ、こういう書き方でもいいんだ、、、」

と、かなりすごいことになっています。

AppleScript名:Spotlightで指定フォルダ以下の指定文字を含むファイル一覧を取得
– Created 2017-03-15 By Shane Stanley
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
–http://piyocast.com/as/archives/4528

set thePath to POSIX path of (path to desktop) – whatever

–ファイル名で検索
set theKeyword to “スクリーンショット”
set fResList to my searchPath:thePath searchPredicate:“(kMDItemFSName CONTAINS [c]%@)” predicateArgs:{theKeyword}
–>  {”/Users/me/Desktop/2013/03/スクリーンショット 2016-12-15 8.44.28.png”, “/Users/me/Desktop/FromDesktop/samples/list splitted or broken/スクリーンショット 2015-08-31 10.49.30.png”, …… }

–UTI(File Kind)で検索
set theKeyword to “public.png”
set fResList to my searchPath:thePath searchPredicate:“(kMDItemContentType == [c]%@)” predicateArgs:{theKeyword}
–> {”/Users/me/Desktop/2013/03/スクリーンショット 2016-12-15 8.44.28.png”, “/Users/me/Desktop/FromDesktop/IMG_3143_resized.png”, “/Users/me/Desktop/FromDesktop/asoctable.png”….}

–ファイルの内容で検索
set fResList to my searchPath:thePath searchPredicate:“(kMDItemTextContent == [c]%@)” predicateArgs:{“戦場の絆”}
–>  {”/Users/me/Desktop/2013/01/gundam_games_history.txt”, “/Users/me/Desktop/book1_2.0.pdf”}

on searchPath:thePath searchPredicate:predString predicateArgs:argList
  set thePred to current application’s NSPredicate’s predicateWithFormat:predString argumentArray:argList
  
set targetURL to current application’s |NSURL|’s fileURLWithPath:thePath
  
set theQuery to current application’s NSMetadataQuery’s new()
  
  
theQuery’s setPredicate:thePred
  
theQuery’s setSearchScopes:{targetURL}
  
theQuery’s startQuery()
  
  
repeat while theQuery’s isGathering() as boolean
    delay “0.001″ as real
  end repeat
  
theQuery’s stopQuery()
  
  
set theCount to theQuery’s resultCount()
  
set theResults to current application’s NSMutableArray’s array()
  
  
repeat with i from 1 to theCount
    set aResult to (theQuery’s resultAtIndex:(i - 1))
    
set thePath to (aResult’s valueForAttribute:(current application’s NSMetadataItemPathKey))
    (
theResults’s addObject:thePath)
  end repeat
  
  
return (theResults’s sortedArrayUsingSelector:“compare:”) as list
end searchPath:searchPredicate:predicateArgs:

★Click Here to Open This Script 

2017/02/12 GPUImageで輪郭抽出フィルタを実行

オープンソースのフレームワーク「GPUImage」(By Brad Larson)を呼び出して、指定画像に輪郭抽出フィルタを実行して、デスクトップにフィルタ名でPNGに保存するAppleScriptです。

テストにあたっては、GPUImageフレームワークをXcodeでビルドして、~/Library/Frameworksフォルダに入れておいてください。

実際にはもっと凝った処理に使っていますが、その過程でフィルタ名を文字列で間接指定して実行するルーチンが出来てきて、なかなか使い勝手もよくなってきています。

気になる処理速度ですが、MacBook Pro Mid 2012 Core i7 2.6GHzの環境で、

 3,264 × 2,448 pixel、1.2MB → 0.7sec
 3,024 × 4,032 pixel、2.8MB → 1.2sec

ぐらいでした(10回計測時の平均。2回目以降はキャッシュが効いている可能性もあります)。処理中にはこの4Core/8Threadの環境でCPUのロードアベレージが20%ぐらいでまだ余裕があり、AppleScriptで並列処理するともうちょっと速度を稼げるかもしれません。

sample1.png

sample2.png

AppleScript名:GPUImageで輪郭抽出フィルタを実行
– Created 2017-02-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “GPUImage” –https://github.com/BradLarson/GPUImage
use framework “AppKit”
–http://piyocast.com/as/archives/4451

set aFile to POSIX path of (choose file of type {“public.image”})
set anImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:aFile
set imgRes to filterWithNSImage(anImage, “GPUImageSobelEdgeDetectionFilter”) of me
set newPath to retUUIDfilePath(aFile, “png”) of me
set sRes to saveNSImageAtPathAsPNG(imgRes, newPath) of me

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

on filterWithNSImage(aNSImage, filterName as string)
  set aClass to current application’s NSClassFromString(filterName)
  
set aImageFilter to aClass’s alloc()’s init()
  
set aProcImg to (aImageFilter’s imageByFilteringImage:aNSImage)
  
return aProcImg
end filterWithNSImage

–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/02/09 Application Supportフォルダを求める

Application Supportフォルダを求めるAppleScriptです。

ただ、通常環境(Script Editorとか)を前提としたものではなく(動きますけど)、Xcode上のCocoa-AppleScript Applet内でCodeSignしてApp Sandboxを有効にした環境におけるApplication Supportフォルダを求めるためのものです。

一応、Script Editorや他のAppleScript開発環境でも動きますが、Sandbox環境では返ってくるパスが全然違います。そうした一種の極限環境でも値を取得していろいろやるための実験であります。

AppleScript名:Application Supportフォルダを求める
– Created 2017-02-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4450

set fileManager to current application’s NSFileManager’s alloc()’s init()
set bundleID to current application’s NSBundle’s mainBundle()’s bundleIdentifier()
set urlPaths to fileManager’s URLsForDirectory:(current application’s NSApplicationSupportDirectory) inDomains:(current application’s NSUserDomainMask)
set appDirectory to (((urlPaths’s objectAtIndex:0)’s URLByAppendingPathComponent:bundleID isDirectory:true)’s |path|()) as string

–> “/Users/me/Library/Application Support/au.com.myriad-com.ASObjC-Explorer-4″–ASObjC Explorer 4の場合
–> “/Users/me/Library/Application Support/com.apple.ScriptEditor2″–Apple純正のScript Editorの場合

★Click Here to Open This Script 

2017/02/09 choose file,choose folderの始点ディレクトリ指定にPOSIX pathが使える

以前からチラホラ話は聞いていたのですが、choose fileやchoose folderの始点ディレクトリ(コマンド実行時にファイル/フォルダ選択ダイアログに表示されるフォルダ)を指定する「default location」オプション。

この「default location」オプションにPOSIX pathが指定できますよ、というお話です。

pathobjects.png

AppleScript/Cocoa関連の主要なパスオブジェクトはだいたいこの図のようになっていて、最近はCocoaに渡すためにPOSIX pathの登場頻度が高くなっています。ただ、よく出てくるとはいっても、「何かに変換するための一時的な形式」であることが多いようです(個人的に)。

このchoose file/choose folderの始点ディレクトリにPOSIX pathが使えるようになっており、おそらくXcode上で作成するCocoa-AppleScript Applet内で事実上aliasが使えないので、その対策として付加された機能と推測しています(根拠はありません)。ちなみに、同環境でaliasが使いたければ、.scpt形式あるいは.scptd形式のScriptとしてXcodeのプロジェクトに入れて、ASOC側からload scriptしつつ呼び出すような処理になると思います(ライブラリにするやりかたもあるかも)。

AppleScript名:choose fileの始点にPOSIX path
choose file default location “/Users/me/Documents”

★Click Here to Open This Script 

AppleScript名:choose folderの始点にPOSIX path
choose folder default location “/Users/me/Desktop”

★Click Here to Open This Script 

始点ディレクトリはフルパスで指定する必要があり、「~」を併用した記法は使えませんでした。

ちなみに、存在しないPOSIX pathを指定すると、

choose_posix_alias.png

のようなエラーが表示されます。コマンド内部ではaliasに変換しているようです。

この仕様がいつごろから実装されているのかはわかりませんが、おそらくmacOS 10.6以降ではないかと推測されます(AppleScript StudioからAppleScriptObjCへの移行期あたり)。

2017/02/03 2つのパスの相対パスを求める v3

2つのPOSIX pathの相対位置を求めるAppleScriptです。aFileの位置から見た、bFileの相対パス表記を求めます。

MacDown(Mac用のMarkdownエディタ)の作者のuranusjrに「ねー相対パス表記サポートしてよー」とお願いしたところ、単に自分の相対パス表記の書き方が間違っていたことが判明。正しい表記で書いたら現行のMacDown v0.6.4でも問題なく画像への相対パスによるリンク指定ができました。

uranusjrもShane Stanleyも誰も悪くありません。自分が間違っていただけです。

というわけで、相対パス計算ルーチンを修正しておきました。

本ルーチンは、実際にMarkdownで記述した書籍本文中の画像リンク(Dropbox上に設定していた)から、画像をローカルの所定のフォルダにすべてダウンロードし、リンク先をローカル(相対パス指定)に書き換えるAppleScriptで使用しました。

AppleScript名:2つのパスの相対パスを求める v3
– Created 2017-01-28 by Takaaki Naganoya
– Modified 2017-01-30 by Shane Stanley
– Modified 2017-02-03 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4436

set aFile to “/Users/me/Documents/Book1/0900 Command Reference/1000 Command References/1010 tell/1010 tell.md”
set bFile to “/Users/me/Documents/Book1/9999_images/002-640×427.png”

set relativePath to calcRelativePath(aFile, bFile) of me
–>  ”../../../9999_images/002-640×427.png”

on calcRelativePath(aPOSIXfile, bPOSIXfile)
  set aStr to current application’s NSString’s stringWithString:aPOSIXfile
  
set bStr to current application’s NSString’s stringWithString:bPOSIXfile
  
  
set aList to aStr’s pathComponents() as list
  
set bList to bStr’s pathComponents() as list
  
  
set aLen to length of aList
  
set bLen to length of bList
  
  
if aLen bLen then
    copy aLen to aMax
  else
    copy bLen to aMax
  end if
  
  
repeat with i from 1 to aMax
    set aTmp to contents of item i of aList
    
set bTmp to contents of item i of bList
    
    
if aTmp is not equal to bTmp then
      exit repeat
    end if
  end repeat
  
  
set bbList to items i thru -1 of bList
  
set aaItem to (length of aList) - i
  
  
set tmpStr to {}
  
repeat with ii from 1 to aaItem
    set the end of tmpStr to “..”
  end repeat
  
  
set allRes to current application’s NSString’s pathWithComponents:(tmpStr & bbList)
  
return allRes as text
end calcRelativePath

★Click Here to Open This Script 

2017/01/31 DragThing上で選択中のSlotのアイコン画像をデスクトップに書き出す

DragThing上で選択中のSlotのアイコン画像をデスクトップに書き出すAppleScriptです。

ランチャーの老舗、James ThomasonのDragThing(バージョン5.9.17)を愛用しています。macOS標準搭載のDockには最小限のアプリケーションのみ登録しておき、基本的にはDragThingから起動。

dt0.png

dt1.png

さまざまなアプリケーションの紹介を行うKeynote書類を作るために、Finder上で選択しておいたアプリケーションのアイコン画像を書き出すAppleScriptは作ってありました。

dt00_resized.png

その一方で、DragThing上の選択中のアプリケーションのアイコンを書き出すようなものはなかったので、DragThing上で選択→Finderに表示→アイコン書き出しScript実行 と操作していたのですが、あまりに不毛なのでDragThing上から直接書き出せるものを作ってみました。

Dockに大量のアプリケーションを登録し、1個1個が豆粒大になっているユーザーを見かけますが、大量のアプリケーションを登録・管理するのであれば、タブでジャンル分けして管理できるDragThingのほうがおすすめです。

また、DragThingはURLも登録しておけます。DragThingをブックマークとして利用することで、Webブラウザの共通ブックマークとして運用できるため、日常的に複数のWebブラウザを利用している人にとってはたいへん便利でしょう。

dt2_resized.png

dt3.png

macOS標準装備のScript Menuに登録して呼び出しています。

dt4_resized.png

AppleScript名:DragThing上で選択中のSlotのアイコンをデスクトップに書き出し v2
– Created 2017-01-31 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4419

tell application “DragThing”
  –Version 5.9.17で動作確認
  
set aSel to selection
  
if aSel = {} then return
  
  
set bSelected to {}
  
repeat with i in aSel
    set j to contents of i
    
try
      –ここでエラーが出ないということは、Dockに登録したファイルである(URLではない)と仮定
      
set aPath to file location of j
      
      
—選択したSlotの内容がアプリケーションだったらデスクトップにアイコン画像を書き出す
      
set fRes to detectAppFile(aPath) of me
      
if fRes = true then
        writeOutICNS(aPath) of me
      end if
    end try
  end repeat
  
end tell

on detectAppFile(aPath)
  tell application “System Events”
    set fType to file type of aPath
    
return (fType as string = “APPL”)
  end tell
end detectAppFile

–指定ファイルのアイコンをCFBundleIdentifierの名称でtmpにPNGで書き出す
on writeOutICNS(aFile)
  set aPOSIXpath to POSIX path of aFile
  
set anURL to current application’s |NSURL|’s fileURLWithPath:aPOSIXpath
  
  
set myBundle to current application’s NSBundle’s bundleWithURL:anURL
  
set aBundleID to myBundle’s bundleIdentifier() as text
  
set anIconFileName to (myBundle’s objectForInfoDictionaryKey:“CFBundleIconFile”) as string
  
  
–アイコン名には拡張子を省略してもよいので、ない場合には補う
  
if anIconFileName does not end with “.icns” then
    set anIconFileName to anIconFileName & “.icns”
  end if
  
  
set ifonFileFullPath to aPOSIXpath & “Contents/Resources/” & anIconFileName
  
  
set sText to “cp “ & quoted form of ifonFileFullPath & ” ~/Desktop/” & quoted form of (aBundleID & “.icns”)
  
do shell script sText
  
end writeOutICNS

★Click Here to Open This Script 

2016/12/05 アニメーションGIFをフレームごとに画像に分解する

アニメーションGIFを指定すると、指定フォルダ内にフレームごとに個別の連番GIF画像に分解出力するAppleScriptです。

AppleScript名:アニメーションGIFをフレームごとに画像に分解する
– Created 2016-11-29 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/4341

set gifFile to POSIX path of (choose file of type “com.compuserve.gif” with prompt “Select Animation-GIF file”)
set destFol to POSIX path of (choose folder with prompt “Select the folder to save gif’s frames”)

set anImage to current application’s NSImage’s alloc()’s initWithContentsOfFile:gifFile
if anImage is equal to missing value then error “Illegal GIF Image”

set anImgRep to anImage’s representations()’s firstObject()
set framesNum to (anImgRep’s valueForProperty:“NSImageFrameCount”) as integer

repeat with i from 0 to (framesNum - 1)
  (anImgRep’s setProperty:“NSImageCurrentFrame” withValue:i)
  
set aRep to (anImgRep’s representationUsingType:(current application’s NSGIFFileType) |properties|:(missing value))
  (
aRep’s writeToFile:(destFol & (i as string) & “.gif”) atomically:true)
end repeat

★Click Here to Open This Script 

2016/11/05 RTF/RTFDを読み込んでテキスト抽出 v2

リッチテキストフォーマットのファイル(RTF/RTFD)を読み込んでテキストを抽出するAppleScriptの修正版です。

以前に掲載していたバージョンでも動いていたので気づかなかったのですが、オプションの指定に間違いがあり、missing valueと指定すべきところをnullと書いていました(汗)。最初の記述のままでもScript Editor上で動作していたものが、osascriptコマンド経由で実行するとかならずWarning Messageが出る。

 「osascript経由だとWarning Messageが出るのはなぜだー?」
 「それはお前のプログラムが間違っているからだー」

というやりとりをAppleのサポートと行って、Shaneに相談したところ「そこ、間違ってるぞ」という結論に(ーー;;

うん、確かに間違ってた。でも、osascriptコマンドには要らないWarning messageが不意に出力されてくるんで(choose fileコマンド実行時にQuickLookプラグインのWarningとか)、それを止められないのかというのがそもそも指摘したかった事項ではあるのですが、、、

あれ? Warnig Messageの発生源だった「MDQuickLook.qlgenerator」を調査してみたら、これが壊れていたので削除。これに起因するWarning Messageは出なくなりました。

AppleScript名:RTFを読み込んでテキスト抽出 v2
– Created 2016-09-29 by Takaaki Naganoya
– Modified 2016-11-05 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4240
–http://piyocast.com/as/archives/4303

set aFile to choose file of type {“public.rtf”}
set aRes to retTextFromRTF(aFile) of me

on retTextFromRTF(aFile)
  set aFilePath to current application’s NSString’s stringWithString:(POSIX path of aFile)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)
  
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTF

★Click Here to Open This Script 

AppleScript名:RTFDを読み込んでテキスト抽出 v2
– Created 2016-09-29 by Takaaki Naganoya
– Modified 2016-11-05 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4240
–http://piyocast.com/as/archives/4303

set aFile to choose file of type {“com.apple.rtfd”}
set aRes to retTextFromRTFD(aFile) of me

on retTextFromRTFD(aFile)
  set aFilePath to current application’s NSString’s stringWithString:((POSIX path of aFile) & “/TXT.rtf”)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTFD

★Click Here to Open This Script 

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

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

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

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

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

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

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

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

★Click Here to Open This Script