Archive for 7月, 2016

2016/07/28 Bookフォルダリネーム

書籍の原稿管理用のAppleScriptです。原稿は章ごとにフォルダ分けしており、

 9999 フォルダ名あるいはファイル名

といったように、仮想ノンブルを前に付けています。この仮想ノンブルで前後関係を制御しており、基本的に数字の大小だけが重要です。

whole.png

最後に1冊分PDFにまとめてビルドするAppleScriptで、書き出し対象のフォルダ以下のMarkdownファイルとPagesのファイルをピックアップし、ファイル名でソート。ソート順は仮想ノンブルによってコントロールされることになります。

Finderの並べ替えを使っているので、シンプルでわかりやすく、使い勝手もよいのですが……ただひとつ難点が。

途中にコンテンツを挿入したい場合にも仮想ノンブルでコントロールしつつ、途中に入るように操作するのですが……場合によってはフォルダおよび書類の仮想ノンブル部分だけリネームする必要が出てきます。

とくに、目下改定作業中の「AppleScript最新リファレンス」のコマンドリファレンス部分は、初版から大幅に改定・補充を行っているため、ひんぱんにリネームが発生して大変です(ーー;;

そこで、こんなScriptを組んでリネーム作業だけ自動化してみました。

Finder上でフォルダを選択して本Scriptを実行すると、選択中のフォルダ名から仮想ノンブルの番号を取得し、

before2.png

内包するフォルダに入っているファイルについても、すべて仮想ノンブル部分を書き換えます。

after2.png

AppleScript名:Bookフォルダリネーム
– Created 2016-07-28 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
# http://piyocast.com/as/archives/4177

set aStep to 10

tell application “Finder”
  set aSel to (selection as alias list)
end tell
if aSel = {} or aSel = “” then return
set aFol to first item of aSel

tell application “Finder”
  –選択中のフォルダの名称を取得
  
set aName to name of aFol
  
  
–選択中のフォルダに入っている書類を取得
  
tell folder (aFol as string)
    set fList to name of every file as alias list
  end tell
end tell

set {startNumStr, folderNameStr} to parseNumAndString(aName) of me
set startNum to startNumStr as integer

set aRes to sort1DListOrder(fList, true) of me –昇順でファイル名をソート

set aFolStr to aFol as string
set aCount to startNumStr
tell application “Finder”
  repeat with i in aRes
    set anAlias to (aFolStr & (i as string)) as alias
    
set tmpName to name of anAlias
    
    
set {curNum, curName} to parseNumAndString(tmpName) of me
    
set newName to (retZeroPaddingText(aCount, length of curNum) of me) & ” “ & curName
    
    
set name of anAlias to newName
    
    
set aCount to aCount + aStep
  end repeat
end tell

–文字列から先頭にあるとおぼしき数値と、スペースで区切られた後続の文字列を分離する
on parseNumAndString(aName)
  set aOffset to offset of ” “ in aName
  
if aOffset = 0 then error “Illigal Format”
  
set aNum to text 1 thru (aOffset - 1) of aName
  
set aText to text (aOffset + 1) thru -1 of aName
  
return {aNum, aText}
end parseNumAndString

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

on retZeroPaddingText(aNum as integer, aDigitNum as integer)
  if aNum > (((10 ^ aDigitNum) as integer) - 1) then return “” –Range Check
  
set aFormatter to current application’s NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setUsesGroupingSeparator:false
  
aFormatter’s setAllowsFloats:false
  
aFormatter’s setMaximumIntegerDigits:aDigitNum
  
aFormatter’s setMinimumIntegerDigits:aDigitNum
  
aFormatter’s setPaddingCharacter:“0″
  
set aStr to aFormatter’s stringFromNumber:(current application’s NSNumber’s numberWithFloat:aNum)
  
return aStr as string
end retZeroPaddingText

★Click Here to Open This Script 

2016/07/27 PDFをページごとに分解してJPEGで保存する v2

PDFをページごとに分解してJPEG画像で保存するAppleScriptのアップデート版です。

実際に連番画像に変換して、ePub書類に変換させてみたらページの順序が狂ってしまいました。連番を振るときにゼロパディングしなかったためだとすぐにわかったので、ゼロパディングの処理を追加したものです。

ただし、実際にはこれだと解像度が不足しており、2倍の解像度で出力するように改良して実戦投入(Retina Display環境に配慮しつつ)しました。

AppleScript名:ASOCでPDFをページごとに分解してJPEGで保存する v2
– Created 2014-12-26 by Takaaki Naganoya
– Modified 2015-09-26 by Takaaki Naganoya
– Modified 2015-10-01 by Takaaki Naganoya
– Modified 2016-07-27 by Takaaki Naganoya–save each PDF page as jpeg
– Modified 2016-07-27 by Takaaki Naganoya–added zero padding function
– 2016 Piyomaru Software
# http://piyocast.com/as/archives/4176

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “QuartzCore”
use framework “AppKit”

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “ページごとに分解するPDFを指定してください”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPOSIXpath to POSIX path of aHFSPath —書き出し先パスをPOSIX pathで用意しておく(あとで加工)

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

set compFactor to 1.0 – 0.0 = max jpeg compression, 1.0 = none

–PDFをページごとに分割してJPEGでファイル書き出し
repeat with i from 0 to (pCount - 1)
  set thisPage to (aPDFdoc’s pageAtIndex:(i))
  
set thisDoc to (current application’s NSImage’s alloc()’s initWithData:(thisPage’s dataRepresentation()))
  
if thisDoc = missing value then error “Error in getting imagerep from PDF in page:” & (i as string)
  
  
set theData to thisDoc’s TIFFRepresentation()
  
set newRep to (current application’s NSBitmapImageRep’s imageRepWithData:theData)
  
set targData to (newRep’s representationUsingType:(current application’s NSJPEGFileType) |properties|:{NSImageCompressionFactor:compFactor, NSImageProgressive:false})
  
set zText to retZeroPaddingText((i + 1), 4) of me
  
set outPath to addString_beforeExtensionIn_addingExtension_(“_” & zText, aPOSIXpath, “jpg”)
  
  (
targData’s writeToFile:outPath atomically:true) –書き出し
end repeat

–ファイルパス(POSIX path)に対して、文字列(枝番)を追加。任意の拡張子を追加
on addString:extraString beforeExtensionIn:aPath addingExtension:aExt
  set pathString to current application’s NSString’s stringWithString:aPath
  
set theExtension to pathString’s pathExtension()
  
set thePathNoExt to pathString’s stringByDeletingPathExtension()
  
  
set newPath to (thePathNoExt’s stringByAppendingString:extraString)’s stringByAppendingPathExtension:aExt
  
return newPath as string
end addString:beforeExtensionIn:addingExtension:

on retZeroPaddingText(aNum as integer, aDigitNum as integer)
  if aNum > (((10 ^ aDigitNum) as integer) - 1) then return “” –Range Check
  
set aFormatter to current application’s NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setUsesGroupingSeparator:false
  
aFormatter’s setAllowsFloats:false
  
aFormatter’s setMaximumIntegerDigits:aDigitNum
  
aFormatter’s setMinimumIntegerDigits:aDigitNum
  
aFormatter’s setPaddingCharacter:“0″
  
set aStr to aFormatter’s stringFromNumber:(current application’s NSNumber’s numberWithFloat:aNum)
  
return aStr as string
end retZeroPaddingText

★Click Here to Open This Script 

2016/07/27 PDFをページごとに分解してJPEGで保存する

指定したPDFをページごとに分解してJPEG画像として保存するAppleScriptです。

書籍のPDFからePubを作ろうとして、さまざまなツールを試して撃沈。日本語のフォントが通らなかったり、オリジナルからかけ離れたレイアウトになったりと散々でした。

ePub版を作るのにそれほど労力を割きたくなかったので、「画像からePub作ろう」と割り切り、PDFをページごとにJPEGに分解することにしました。これを手作業で行っていたのでは日が暮れます。

splittedjpegs.png

そこで、本AppleScriptを作成。ありものを組み合わせたぐらいの作業で完成。さくっとPDFをページごとのJPEG画像に分解できました。

AppleScript名:ASOCでPDFをページごとに分解してJPEGで保存する
– Created 2014-12-26 by Takaaki Naganoya
– Modified 2015-09-26 by Takaaki Naganoya
– Modified 2015-10-01 by Takaaki Naganoya
– Modified 2016-07-27 by Takaaki Naganoya–save each PDF page as jpeg
– 2016 Piyomaru Software
# http://piyocast.com/as/archives/4174

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “QuartzCore”
use framework “AppKit”

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “ページごとに分解するPDFを指定してください”)
set aPOSIX to POSIX path of aHFSPath
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)

set aPOSIXpath to POSIX path of aHFSPath —書き出し先パスをPOSIX pathで用意しておく(あとで加工)

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

set compFactor to 1.0 – 0.0 = max jpeg compression, 1.0 = none

–PDFをページごとに分割してJPEGでファイル書き出し
repeat with i from 0 to (pCount - 1)
  set thisPage to (aPDFdoc’s pageAtIndex:(i))
  
set thisDoc to (current application’s NSImage’s alloc()’s initWithData:(thisPage’s dataRepresentation()))
  
if thisDoc = missing value then error “Error in getting imagerep from PDF in page:” & (i as string)
  
  
set theData to thisDoc’s TIFFRepresentation()
  
set newRep to (current application’s NSBitmapImageRep’s imageRepWithData:theData)
  
set targData to (newRep’s representationUsingType:(current application’s NSJPEGFileType) |properties|:{NSImageCompressionFactor:compFactor, NSImageProgressive:false})
  
  
set outPath to addString_beforeExtensionIn_addingExtension_(“_” & (i + 1) as string, aPOSIXpath, “jpg”)
  
  (
targData’s writeToFile:outPath atomically:true) –書き出し
end repeat

–ファイルパス(POSIX path)に対して、文字列(枝番)を追加。任意の拡張子を追加
on addString:extraString beforeExtensionIn:aPath addingExtension:aExt
  set pathString to current application’s NSString’s stringWithString:aPath
  
set theExtension to pathString’s pathExtension()
  
set thePathNoExt to pathString’s stringByDeletingPathExtension()
  
  
set newPath to (thePathNoExt’s stringByAppendingString:extraString)’s stringByAppendingPathExtension:aExt
  
return newPath as string
end addString:beforeExtensionIn:addingExtension:

★Click Here to Open This Script 

2016/07/26 PFSystemKitでシステムの詳細な情報を取得

オープンソースのシステム情報取得フレームワーク「PFSystemKit」(Perceval Faramaz氏作)を呼び出して、Macのモデル、CPU、バッテリー、RAMについての詳細な情報を取得するAppleScriptです。

PFSystemKitをビルドして(苦労せずにビルドできます)、~/Library/Frameworksフォルダにインストールすれば使えます。

AppleScript名:PFSystemKitでMac本体の情報を取得
– Created 2016-07-26 by Takaaki Naganoya
– 2016 Piyomaru Software
# http://piyocast.com/as/archives/4173
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “PFSystemKit”

set systemKit to current application’s PFSystemKit’s investigate()

systemKit’s platformReport()’s serial() as string
–>  Serial Number

systemKit’s platformReport()’s model() as string
–>  Mac Model Name and version

systemKit’s platformReport()’s family() as integer
–>  5

systemKit’s platformReport()’s uuid() as string
–>  uuid

systemKit’s platformReport()’s memorySize() as integer
–>  8

systemKit’s platformReport()’s boardID() as string
–>  Motherboard ID

systemKit’s platformReport()’s romVersion() as string
–>  Rom version

systemKit’s platformReport()’s romReleaseDate() as date
–>  4015/9/11

systemKit’s platformReport()’s smcVersion() as string
–>  ”2.3f36″

systemKit’s platformReport()’s sleepCause() as string
–>  ”5″

systemKit’s platformReport()’s shutdownCause() as string
–>  ”3″

systemKit’s platformReport()’s platform() as string
–>  ”1″

systemKit’s platformReport()’s endianness() as string
–>  ”0″

★Click Here to Open This Script 

AppleScript名:PFSystemKitでCPUの情報を取得
– http://piyocast.com/as/archives/4173
– Created 2016-07-26 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “PFSystemKit”
–https://github.com/perfaram/PFSystemKit

set systemKit to current application’s PFSystemKit’s investigate()

systemKit’s cpuReport()’s vendor() as string
–>  ”GenuineIntel”

systemKit’s cpuReport()’s |count|() as integer
–>  1

systemKit’s cpuReport()’s coreCount() as integer
–>  4

systemKit’s cpuReport()’s threadCount() as integer
–>  8

systemKit’s cpuReport()’s frequency() as real
–>  2.6

systemKit’s cpuReport()’s L2Cache() as real
–>  0.25

systemKit’s cpuReport()’s L3Cache() as real
–>  6.0

★Click Here to Open This Script 

AppleScript名:PFSystemKitでRAMの情報を取得
– http://piyocast.com/as/archives/4173
– Created 2016-07-26 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “PFSystemKit”
–https://github.com/perfaram/PFSystemKit

set systemKit to current application’s PFSystemKit’s investigate()

systemKit’s ramReport()’s wired() as string –?
–>  ”0″

systemKit’s ramReport()’s active() as string –?
–>  ”0″

systemKit’s ramReport()’s inactive() as string –?
–>  ”0″

systemKit’s ramReport()’s free() as string –?
–>  ”0″

systemKit’s ramReport()’s stats() as string
–>  ”3.491069952E+96.260580352E+95.022162944E+9276824064″

systemKit’s ramReport()’s total() as string
–>  ”8″

★Click Here to Open This Script 

AppleScript名:PFSystemKitでbatteryの情報を取得
– http://piyocast.com/as/archives/4173
– Created 2016-07-26 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “PFSystemKit”
–https://github.com/perfaram/PFSystemKit

set systemKit to current application’s PFSystemKit’s investigate()

set aBattV to systemKit’s batteryReport()’s voltage() as integer
–>  12224

set aBattT to systemKit’s batteryReport()’s temperature() as integer
–>  31

set aBattM to systemKit’s batteryReport()’s manufacturer() as string
–>  ”SMP”

set aBattC to systemKit’s batteryReport()’s cycleCount() as integer
–>  404

set aBattA to systemKit’s batteryReport()’s age() as integer
–>  1475

set aBattS to systemKit’s batteryReport()’s serial() as string
–>  ”X99999999XXXXXXXX”

set aBattCV to systemKit’s batteryReport()’s manufactureDate() as date
–>  date “2012年7月12日木曜日 0:00:00″

set aBattCV to systemKit’s batteryReport()’s designedCycleCount() as integer
–>  1000

set aBattCV to systemKit’s batteryReport()’s model() as string
–>  ”bq20z451″

set aBattCV to systemKit’s batteryReport()’s amperage() as integer
–>  2603

set aBattCV to systemKit’s batteryReport()’s currentCapacity() as integer
–>  4130

set aBattCV to systemKit’s batteryReport()’s maxCapacity() as integer
–>  6335

set aBattCV to systemKit’s batteryReport()’s health() as real
–>  0.0

set aBattCV to systemKit’s batteryReport()’s power() as real
–>  31.819072

★Click Here to Open This Script 

2016/07/20 PDFを回転させて新規保存 v2

PDFを時計周りに任意の角度で回転させて新規保存するAppleScriptです。角度は90度単位で指定可能です。

AppleScript名:ASOCでPDFを回転させて新規保存 v2
– Created 2015-10-20 by Takaaki Naganoya
– Modified 2016-07-01 by Takaaki Naganoya–複数回PDFに回転処理を行った場合の挙動を改善
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”

set aPath to POSIX path of (choose file of type {“com.adobe.pdf”} with prompt “Select PDF”)
set newFile to POSIX path of (choose file name)

set pdfRes to rotatePDFandSaveAt(aPath, newFile, 90) of me

–oldPath and newPath have to be a POSIX path, aDegree have to be in {0, 90, 180, 270, 360}
on rotatePDFandSaveAt(oldPath as string, newPath as string, aDegree as integer)
  
  
–Error Check
  
if aDegree is not in {0, 90, 180, 270, 360} then error “Wrong Degree”
  
  
set aURL to current application’s |NSURL|’s fileURLWithPath:oldPath
  
set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
  
  
set pCount to aPDFdoc’s pageCount() –count pages
  
  
–Make Blank PDF
  
set newPDFdoc to current application’s PDFDocument’s alloc()’s init()
  
  
–Rotate Each Page
  
repeat with i from 0 to (pCount - 1)
    set aPage to (aPDFdoc’s pageAtIndex:i)
    
    
–Set Degree
    
set curDegree to aPage’s |rotation|() –Get Current Degree
    (
aPage’s setRotation:(aDegree + curDegree)) –Set New Degree
    
    (
newPDFdoc’s insertPage:aPage atIndex:i)
  end repeat
  
  
set aRes to newPDFdoc’s writeToFile:newPath
  
return aRes as boolean
  
end rotatePDFandSaveAt

★Click Here to Open This Script 

2016/07/18 PDFの指定ページを削除

指定PDF中の指定ページを削除するAppleScriptです。

CocoaのAPIをひととおり調べて、PDFのページ削除を行うメソッドなどが存在していないことがよくわかりました。

存在しない=できない、ということではないのでAppleScriptで組んでみました。動作確認した範囲ではちゃんと機能しています。

pdfremove.png

削除機能を削除機能として考えただけでは実現できませんが、これを「新規PDFへのページコピー」と考えれば不可能ではありません。つまり、削除を「新規PDFにコピーしない」ことと定義し直してみました。

 /卦PDFに指定ページ以外のページをコピー
 ▲リジナルのPDFを削除
 新規PDFをオリジナルのPDF名で保存

と処理すれば、指定ページを削除したのと同じことです。

このルーチンを用いて、「複数ファイルのPDFを連結、末尾が空白ページだったら削除しつつ連結」という動作を行うAppleScriptを簡単に書くことができました。

ただ、この処理方法がSandbox環境で(Xcode上で作成するCocoa-AppleScript Applet内で)許可されるものなのかは、試してみないといけないでしょう。

AppleScript名:PDFの指定ページを削除
– Modified 2016-07-18 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
use framework “QuartzCore”

set inFile to (choose file of type {“pdf”} with prompt “Choose your PDF files:”)
set targPage to 2
set maxPage to pdfPageCount(inFile) of me
if 0 < targPage and targPage maxPage then
  –Skip
else
  display dialog “Page Number Range Error”
  
return
end if

removeSpecificPageInPDF(inFile, targPage) of me

on removeSpecificPageInPDF(inFile, targPageNum)
  – make URL of the first PDF
  
set inNSURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of inFile)
  
set theDoc to current application’s PDFDocument’s alloc()’s initWithURL:inNSURL
  
  set oldDocCount to ((theDoc’s pageCount()) - 1)
  
  –Make Blank PDF (deleted PDF)
  
set newPDFdoc to current application’s PDFDocument’s alloc()’s init()
  
  set newDocCount to 0
  
  repeat with i from 0 to oldDocCount
    if i is equal to (targPageNum - 1) then
      log {“skip page at:”, i}
    else
      log {i}
      
set thePDFPage to (theDoc’s pageAtIndex:i) – zero-based indexes
      (
newPDFdoc’s insertPage:thePDFPage atIndex:newDocCount)
      
set newDocCount to newDocCount + 1
    end if
  end repeat
  
  –元ファイルを削除して問題がなければ、指定ページを削除したPDFを同名で新規保存
  
set aRes to deleteFile(inFile) of me
  
if aRes = true then
    set aRes to (newPDFdoc’s writeToURL:inNSURL)
  end if
  
  return aRes
  
end removeSpecificPageInPDF

–指定PDFのページ数をかぞえる
on pdfPageCount(aFile)
  set aFile to POSIX path of aFile
  
set theURL to current application’s |NSURL|’s fileURLWithPath:aFile
  
set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:theURL
  
set aRes to aPDFdoc’s pageCount()
  
return aRes as integer
end pdfPageCount

–指定ファイルの削除
on deleteFile(aFile)
  set aPath to POSIX path of aFile
  
set filePath to current application’s NSString’s stringWithString:aPath
  
set fileManager to current application’s NSFileManager’s defaultManager()
  
set aRes to fileManager’s removeItemAtPath:filePath |error|:(reference)
  
–>  {true, missing value}
  
–>  {false, (NSError) Error}
  
copy aRes to {aFlag, aReason}
  
return aFlag
end deleteFile

★Click Here to Open This Script 

2016/07/17 PDFの最終ページのみ切り出して空白かどうかチェック

指定のPDFの最終ページのみチェックして、空白であるかどうかを返すAppleScriptです。

ASOCが使えるようになって、AppleScriptだけでたいていのPDF処理ができるようになりました。ページ数のカウント、複数ドキュメントの連結、ページごとの分割、回転、パスワードの設定や解除、テキスト抽出、などなど。

pdf_blank_page.png

しかし、いまだにできていなかった処理が「指定ページが空白かどうかのチェック」でした。指定ページからテキスト抽出しただけではダメで、画像だけでテキストが存在しないケースに対応する必要があります。

最初にAppleScriptでコレを実装したときには、割と力技でやってしまいました。Photoshopでオープンしてラスタライズし、ヒストグラムを取得してページ上に文字や画像が存在しているかどうかを検出していました。白くないピクセルが存在していたら、何らかのオブジェクトが存在しているだろう、という判断方法です。

ただ、Photoshopが存在しないと処理できないため、ASOCだけでなんとかできないかと試行錯誤。結論からいえば、現時点ではちょっと無理な感じがします。ただ、Photoshopを使わないことがそもそものテーマなので、フリーのMuPDFをみつけてHomebrew経由でインストール。これを呼び出すようにしてみました。指定ページの画像オブジェクトと埋め込みフォント情報を取得して、画像やテキストが存在しないかどうかを確認します。

そもそもなんでこれが必要になったかといえば、書籍の多くのページをMarkdownで記述しており、PDF書き出し時に意図しない空白ページが生成されるケースが割とあるので、制御不可能なMarkdownと格闘するよりも、書き出したPDFに対して末尾ページの空白チェックを行って削除したほうがいいと判断したからです。

AppleScript名:PDFの最終ページのみ切り出して空白かどうかチェック
– Created 2016-07-17 by Takaaki Naganoya
– 2016 Piyomaru Software
– At first, install mutool via homebrew by “brew install mupdf-tools” from Terminal.app
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”

set aHFSPath to (choose file of type {“pdf”} with prompt “Choose your PDF file:”)
set aRes to detectTheLastPageIsEmpty(aHFSPath) of me
–> true

–指定PDFの最終ページが空白かどうか検出する
on detectTheLastPageIsEmpty(aHFSPath)
  set aPOSIX to POSIX path of aHFSPath
  
set aURL to (current application’s |NSURL|’s fileURLWithPath:aPOSIX)
  
copy aPOSIX to aPOSIXpath —出力用に複製
  
  
set aPDFdoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL
  
set pCount to aPDFdoc’s pageCount()
  
  
–PDFの最終ページを分割してファイル書き出し
  
set thisPage to (aPDFdoc’s pageAtIndex:(pCount - 1))
  
set thisDoc to (current application’s PDFDocument’s alloc()’s initWithData:(thisPage’s dataRepresentation()))
  
set outPath to addString_beforeExtensionIn_(“_lastpage”, aPOSIXpath)
  (
thisDoc’s writeToFile:outPath) –書き出し
  
  
–最終ページ内の画像ファイル、埋め込みフォントの情報を取得
  
try
    set aRes to do shell script “/usr/local/bin/mutool extract “ & quoted form of outPath
  on error erM
    copy erM to aRes
  end try
  
  
–切り出したPDFの削除はかならず行っておく
  
try
    do shell script “rm -f “ & quoted form of outPath
  end try
  
  
if aRes“” then
    return false –Not Empty
  else
    return true –Empty
  end if
end detectTheLastPageIsEmpty

–ファイルパス(POSIX path)に対して、文字列(枝番)を追加。拡張子はそのまま
on addString:extraString beforeExtensionIn:aPath
  set pathString to current application’s NSString’s stringWithString:aPath
  
set theExtension to pathString’s pathExtension()
  
set thePathNoExt to pathString’s stringByDeletingPathExtension()
  
set newPath to (thePathNoExt’s stringByAppendingString:extraString)’s stringByAppendingPathExtension:theExtension
  
return newPath as string
end addString:beforeExtensionIn:

★Click Here to Open This Script 

2016/07/16 as item?

US Appleが主催しているML「AppleScript-Users」において、珍しいものを見つけました。MLの内容についてはつねに分類・評価・分析を行い、分類については自動で仕分けを行うロボットScriptが稼働しています。

そこまでやっても、有用な情報は人間の目で見て判別する必要があるわけですが……なにげないやりとりの中に強い違和感が。

list1.png

こう書いて、思い通りにSafariの各Windowのタイトルが文字列で帰ってこないよ的な、初心者レベルな質問がありました。これについては、

list2.png

のように、contents ofで実際の内容を取得してから、nameをとってくるのが「定石」です。

ところが、

list3.png

のように、「as item」といった書き方ができるようで。これはcontents ofと同じ挙動を行う書き方のようです。こんな書き方ができるとは思ってもみませんでした。このケースでは「as text item」も通ります(同じ実行結果に)。

list4.png

実行速度を計測してみたところ、誤差範囲内でとくに速度は変わりません。

2016/07/14 (業務連絡)書籍のアップデートに向けて

技術書典で販売した電子書籍を、目下絶賛アップデート中です。「AppleScript最新リファレンス」は354ページ、「最新事情がわかる AppleScript 10大最新技術」は目下128ページになっています。両方ともまだまだ増えそうな気配です。7月末をメドに作業をすすめております。

f04d20f9-284d-4924-9423-39c666432ec7.jpg
▲クリックで拡大
58a7125a-fb9c-4455-90e9-ead152073830.jpg
▲クリックで拡大

さてここで、「技術書典」でお買い上げいただいた皆様には、アップデートに向けて1つ大事な確認があります。これらのうち両方、あるいはどちらかをお買い上げいただいたかを確認する必要があるということです(すんません!)。

えーーつまりーー、ご連絡用にメールアドレスをご記入いただいているわけですが、それには「どの本をお買い上げいただいたか」を完全に記載してある状態ではなく、その確認をしなくちゃいけないよねというところなのであります。

そこで、こちらから確認用のAppleScriptアプレットをメールに添付してお送りいたします(怪しくない証拠に、コード署名しておきます)。このアプレットに電子書籍のPDFファイルをドラッグ&ドロップしていただくと、ファイルのチェックサムを計算してクリップボードに入れるようにしておきます。

この計算結果をメールにペーストしてご返送いただく、ということで考えています。なるべくオリジナルのファイルで処理していただくということで、ひとつお願い申し上げます。

Macをもってないよ! という方には、その旨お返事いただければ、対処できるように考えているところです。

これは、将来への布石でもあります。同様のシステムでアップデートのご案内→確認→アップデート版のURLをお送りする、というフローを検討中です。

→ アップデート版を販売開始しました

2016/07/12 Absolute Timeを取得

2001年1月1日 00:00:00からの相対時間であるAbsolute Timeを求めるAppleScriptです。

なんでこんなものが必要になったのかといえば、Spotlightでファイル作成日時を指定して検索したいと思ったときに、例によってFinder上でSpotlight検索を行い、その内容を保存。

保存すると紫色のアイコンである「保存された検索クエリー」になるので、Command-iで情報を見るとかんたんにmdfindの検索パラメータが確認できます。

saved_search.png

なじみのない数値が……。そこで、1年の秒数(3600×24×365)で割ってみたところ、2000年近辺の数字が出てきました。

そのため、2000年あたりからの相対秒だと当たりをつけて、Appleのオンラインドキュメントを検索してみたところ、大当たり。これで、(実行時の)現在時刻からAbsolute Timeを求めることができました。ただ求めただけでは、AppleScriptの数値では指数表示になってしまうため、なるべくオリジナルのまま壊さないように文字列化してみました。

Absolute Timeを求める方法はほかにもありそうなので、探してみることとしましょう。

この記述を用いて、「指定フォルダ内のPages書類とMarkdown書類をデスクトップ上にPDF出力し、それを順番に連結して1つのPDFに出力。デスクトップ上に書き出した不要なPDFは削除する」というAppleScriptを仕上げることができました。

つまり、電子ブック1冊分を完全自動でビルドできるようになったわけで(以前は、書類ごとのPDFを書き出すプロセスとPDFの合成プロセスが別々)、たいへんにけっこうなことです。

AppleScript名:Absolute Timeを取得
– Created 2016-07-12 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

–タイムスタンプ取得(Jan 1 2001 00:00:00 GMTからの相対秒、Absolute Timeで取得)
set aTime to current application’s NSString’s stringWithFormat_(“%@”, current application’s CFAbsoluteTimeGetCurrent()) as string
–>  ”490022703.57607″

★Click Here to Open This Script 

2016/07/10 マウスカーソルのグローバルな現在座標を取得する

マウスカーソルのグローバルな現在座標を取得するAppleScriptです。

複数モニタを接続している状態で、すべてのモニタを組み合わせたグローバルなマウスカーソルの位置座標を取得するAppleScriptです。ただし、得られる座標系はCocoaの座標系であることに注意が必要です。

AppleScriptの座標系はメインモニタの左上が{0, 0}であるため、GUI Scriptingや各種アプリケーションのウィンドウの位置や大きさなどと合わせるためには若干の計算が必要になります。

AppleScriptでも、InDesign、Illustrator、Photoshopなどアプリケーションによって座標系は変わってきますので、そのあたりは常に注意する必要があります。

corrdinate_resized2.png

AppleScript名:ASOCでマウスカーソルのグローバルな現在座標を取得する
– Created 2016-07-10 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aLoc to current application’s NSEvent’s mouseLocation()
set {xPos, yPos} to {x of aLoc, y of aLoc}
return {xPos, yPos}

★Click Here to Open This Script 

2016/07/07 コンテクストメニュー・アシスタント(1)

「技術書典」で販売した書籍を買ってくださった方限定のダウンロードコンテンツ「コンテクストメニュー・アシスタント」をご用意しています。

本来は同イベントが終わったあとの週明けにでもご案内すべく、メールアドレスを手打ちして準備していたのですが、この「コンテクストメニュー・アシスタント」、かれこれ10年ぐらい昔に作り始めて(OS X 10.4の時代ですね)久しぶりにすべての機能をチェックしだしたら、現行の環境でうまく動かないものもあったりして、目下絶賛メンテナンス中です。

具体的に、この「コンテクストメニュー・アシスタント」とは何かといえば、スクリプトエディタのコンテクストメニューから特定のフォルダ(/Library/Scripts/Script Editor Scripts/)にあるAppleScriptを呼び出せるようになっており、その仕組みを利用した強化Script群のことです。

scriptmenuassistant1_resized.png

OSのデフォルトの状態ではAppleが用意したScriptが呼び出せるようにはなっています。

ただ、Apple純正のScriptだと「あんなこともできない」「こんなこともできない」という状態。

自分はこれを魔改造して、だいたいのAppleScriptの構文要素をコンテクストメニュー経由で入力できるようにしてあるため、これが入っていない環境だと生産性が思いっきり落ちます。「AppleScriptリファレンス」を持ち歩かなくてすむように作ったので、これがあれば参考書は必要ありません。

書き換えが終了したものをまずはご紹介しましょう。

operation.png

・Magic Tools/オペレーション/シャットダウン/これが終わったら

最前面のAppleScriptを実行し、実行が終了したらMacを自動でシャットダウンするものです。実行に時間のかかるScriptを実行して、終了前に先に帰宅したい場合に有効です。

・Magic Tools/オペレーション/シャットダウン/これをn分後に実行して

最前面のAppleScriptを、指定分後に実行してシャットダウンするものです。タイマーがわりに使っていました。もちろん、現在のMacではシャットダウンよりもスリープが推奨されているので、同様のScriptでスリープするものも用意しています。

・Magic Tools/オペレーション/シャットダウン/これを指定時刻に実行

最前面のAppleScriptを、指定時刻まで待機して実行するものです。セキュリティが厳しい仕事相手で、20時以降でないと先方のサーバーにアクセスできない、とかいった場合にこれで時刻指定して実行し、結果だけTwitterやメールで受け取るような運用をしています。

・Magic Tools/オペレーション/シャットダウン/すべて実行したら

スクリプトエディタでオープン中のAppleScriptをすべて実行したらシャットダウンを行うものです。それなりに大きいAppleScriptを組んでいると、それらを結合させる手間がもったいなくて、けっこうバラバラに動かしていたりすることがあり、ひととおり実行終了するまでマシンの前に貼り付いているのがイヤだったので作りました。

こうした運用系のツールは、「とっとと帰宅する」ことを目的として作ったものが多いですね。

本アシスタントは、ASOC登場以前に作られたものなので、ASOCなりAppleScript Librariesの存在を前提にすると、またまったく別のものが考えられると思います。

PDF処理ひとつとっても、昔はできなかったようなPDFの連結や分離、テキストの抜き出し、画像からPDFへの変換、指定フォルダ内のPDFの1ページ目のみ抽出・・・などなど、すべてAppleScriptだけで高速に実現できるようになっており、もはやOS X 10.10以前には戻れません。

話をコンテクストメニュー・アシスタントに戻しますが、「技術書典」だけでなくオンライン販売版(こちらもメンテナンス中)の書籍にもこれを付けますので、ご安心を。

2016/07/04 delay命令のパラメータが0.001秒まで対応

Mac OS X 10.3, Pantherで、delayコマンドのパラメータが0.1秒単位での指定に対応しました。10.3の頃はAppleScriptのバグがとても多かったので、「指定できても無視されるんでしょ?」ぐらいにしか受け取っていませんでした。

本サイトでもdelay (”0.001″ as real)などの記述もあり、「本当にそこまで厳密に時間待ちしないが、最小限の単位時間の時間待ちを意図している」などと説明していました。

AppleScript Language Guideでは、

『delayコマンドは時間待ちの時間の長さを保証しない。60分の1秒(0.016秒)以下では精度がさらに落ちる。delayコマンドはオーディオやビデオのシンクロなどの実時間を反映させた処理には不向きである。』

という説明が行われています。あれ? Appleの公式ドキュメントの記述は、裏を返せば0.016秒までは指定できるということを書いていますね。

そうはいっても、つい最近まではAppleScriptではまともに秒以下の単位の計測が行えなかったため、見向きもされていませんでした(本当)。

が!(大事なところです)

本当に現行の環境で実測したことはなかったのではないか? などと疑念が湧いて出てきたので、実際に計測してみました。

その結果、0.001秒までは指定したとおりに時間待ちしていることが判明。ここに訂正いたします。OS X 10.11のMacBook Pro Retina 2012では0.001まで指定してその通りに時間待ちしてくれました。

Mac mini Late 2014(2.6GHz, Core i5) OS X 10.10.5で実測したところ、こちらも0.001秒まで時間待ちしてくれました。

MacBook Air Mid 2011(1.6GHz, Core i5)OS X 10.11で実測したところ、CPUのパワーの低さもあるのか0.001秒を指定すると0.002秒ぐらいの値になることが確認されました。

delay命令による0.001秒オーダーの時間待ちについては、実行環境によって精度が左右されるようです。

AppleScript名:delay 0.1
– Created 2016-07-04 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
delay 0.1
set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat) as real

–>  0.101064026356

★Click Here to Open This Script 

AppleScript名:delay 0.01
– Created 2016-07-04 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
delay 0.01
set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat) as real

–>  0.011389970779

★Click Here to Open This Script 

AppleScript名:delay 0.001.scptd
– Created 2016-07-04 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
delay “0.001″ as real
set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat) as real

–>  0.001370966434

★Click Here to Open This Script