[misc][in Japanese] iTunes COM SDKをJScriptで使う方法
ディスクを増設して、iTunesに登録していた音楽ファイルを移動したら、iTunesから認識されなくなった。iTunesから全削除して、再登録するのが王道らしいが、それだと再生回数や最終再生日時といった情報が消えてしまうのがちょっと困る。
なんとかならないかと調べたら、iTunesの各種機能を自作プログラムから呼び出すことができるらしい。というわけで、iTunes COM for Windows SDKなるものをダウンロードしていじってみた。WSH,JScript,COMオブジェクト、といった環境を使うのは初めてだったので、iTunes COM SDKを使うために自分が仕入れた知識をメモしておく。
- iTunes COM SDKの中に入っているサンプルスクリプトは、JScriptで書かれている。
- JScriptは、WSH(Windows Script Host)という実行環境で実行可能。
- スクリプトをWSHで実行したい場合は、wscript HOGE.js (GUIベース)または、cscript HOGE.js(コマンドラインベース)を、実行すればよい。自分は、cygwinから、cscriptを実行してみたが特に問題なく実行できるようである。
- 同梱のヘッダファイルは、IDLファイルと呼ばれるインタフェース記述専用言語で書かれている(といっても、ほとんどCのヘッダファイル)。
- 引数についているin,out,retvalなどの修飾子のうち"retval"というのは、「戻り値を直接扱わない言語からの呼び出し時にこの引数が戻り値になる」という意味。JScriptはこれに該当する。HRESULTな戻り値にアクセスする方法は見つからなかった。
なお、COMインタフェースの記述は、IDLという専用の記述言語(といっても、ほとんどCのヘッダファイルだが…)で行われている。また、WSHは、Windows Powershellに移行されているようで、今後は更新されないらしい。
これでやっといじれるようになったので、あれこれ試してみたが、iTunes COMオブジェクト経由では、デッドリンクになっているファイルのパスは取れないらしい。iTunesからは見られるのに…。COMオブジェクト経由だと、ご丁寧に、ファイルの実体の存在確認をしてからパスを返す模様。
また、プレイリストにAddTrackしようとしても、デッドリンクになっているトラックはAddTrackされない。これも、iTunes上で手動では可能なのに…。
参考にした主なサイトは以下の通り。
- Windows管理者のためのWindows Script Host入門
- サイブリッジラボの記事
- iTunes SDK覚え書き(C++から使っているがコードサンプルが豊富)
PDFを生成するCGIの検討
書き込めるスペースが広いカレンダーを自作したくなった。いろいろ考えたが、CGI化を視野に入れて技術検討をしてみた。
フォントの検討
- 埋め込むフォントの元ファイルは、CGIと同じサーバ上に複製を置く必要があり、ライセンスの確認が必要。
- フォントのライセンスをまとめているサイトとして参考にしたのは以下。
- 無償で商用利用できて品質の高い日本語フォント一覧(Liner Note)(和文フォント, 有償フォントも含まれていて便利)
- 商用サイトでも無料で利用できる日本語のフリーフォント集(coliss内)(和文フォント)
- unifont.org(欧文フォント, ライセンス上Linuxに組み込めそうなもののみ)
- FONTSPACE(欧文フォント, やたらと豊富)
- Font Squirrel(欧文フォント)
最終的に、和文を梅ゴシック(蓬莱和多流作成)、欧文をLiberation Sans(Red Hat社提供)にすることにした。
祝日の判定ロジックの検討
自作できそうな気がしてしまうが、ここ10年ほどの頻繁な制度変更を見ていると、制度に追従するのは困難と思われるので探してみた。PHP用のライブラリがあった。下記で、祝日かどうかと、祝日の名称が分かる。
Excelで文字化けを逆変換する
中国語や韓国語のテキストが、日本語と誤認識されてしまうと、意味不明な文字列になる。これは、中国語EUC(通称GB2312)や韓国語EUC(通称KS5601)で書かれたテキストが、日本語EUCとして解釈されるために起こる。
テキストファイルやHTMLファイルなら回復の手段も色々あるが、Microsoft Officeしか使われないようなオフィス環境では、説明するのに苦労する。
そこで、Excelのセルに入力された文字列を、逆変換する関数を作った。
下記ページのサンプルを大いに参考にさせていただき、ADODB.Streamクラスを使って簡単に実現できた。
http://homepage2.nifty.com/nonnon/SoftSample/SampleModADOS.html
Excelから、Visual Basic Editorを起動してから、「挿入」「標準モジュール」としたところに、下記のコードを貼り付ければ、セルの中でGBRECOVER(String str)という関数が使えるようになる。
Public Function GBrecover(ByRef strIn As String) As String On Error GoTo ErrHandler Dim objStm As Object Set objStm = CreateObject("ADODB.Stream") objStm.Open objStm.Type = 2 'adTypeText objStm.Charset = "euc-jp" objStm.WriteText strIn objStm.Position = 0 objStm.Charset = "GB2312" GBrecover = objStm.ReadText() objStm.Close Set objStm = Nothing Exit Function ErrHandler: Debug.Print "文字コード変換エラー: " & Err.Description If objStm Is Nothing = False Then objStm.Close Set objStm = Nothing End Function
上記コード中の2箇所のCharsetの設定を変更すれば、KS5601の文字化け逆変換なども簡単に作れる。使用可能なCharsetの一覧は、regeditを起動して、下記のKeyを探せばよい。
HKEY_CLASSES_ROOT\MIME\DataBase\Carset
SQLite3の検討
日次で出力される、支店別売上げのcsvデータがあるとする。yyyymmdd.csvというファイル名で、「支店コード」「支店地区名」(カンマを含むことがある)「売上げ」の3項目が入っているものとする。このデータを1ヶ月分とか1週間分とか支店別に集計したり、曜日別の傾向を見るなど、いろいろと加工したい。
当初は、ちまちまとPerlスクリプトを作っていたのだが、色々な集計をtrial & errorで実行したくなってくると(曜日別に集計して、イマイチ傾向がなさそうなので、所属地区を絞ってみようとかそういう感じ)、いちいちPerlスクリプトを作ったり直したりするのは効率が悪く感じるようになった。そこで、SQLを使える環境を検討してみた。
上記の通り、基本的に自分の作業用であり、データ量もそれほど多くないことから、MySQLやPostgreSQLなどのクライアント・サーバタイプのDBMSは大げさすぎるし、これだけのためにサーバを構築するのも面倒だったので、単体で動作するSQLite3なるものを調べてみた。日本語の情報源としては、SQLite3をつかってみようが分かりやすかった。
自分にとっての利点は以下の通り。
ただ、調査の結果、以下の欠点が判明したので、導入を躊躇している。
- データ中にカンマを含むcsv(カンマを含むデータは、ダブルクオートで囲まれている)が扱えない。事前にTAB区切りにでも変更する必要がありそう。Perlを使ったcsv2tsvというスクリプトは発掘した。
- 日付型がない。もちろん、DB中にはExcel風に1970/1/1からの経過日数としてINTEGER型で保持しておくなど、色々な対応は可能。ただ、SQLite3の日付関係の関数のドキュメントがほとんどなく、実際に日付をどう扱えば便利なのかが分からない。日付による絞込みを頻繁に使うので、そのたびに select datetime(dateval) where dateval > datetime("2008-10-15") などと書くのは面倒だし。
次は、MySQLを検討してみるつもり。XAMPPがあるので、実はサーバ構築しなくてもすぐに使えることを思い出したので。
使い勝手の悪いWebサイトを勝手に改造する(Greasemonkey)
仕事で日常的に使わなくてはならない、社内WebサイトのUIが使いにくい。Prevボタンさえ付けてくれれば……、と思うが、こういう要望を出しても、すぐに対処してもらえるとは限らない(そもそも要望を受ける窓口がないこともある)。
そういうときには、FirefoxのAdd-onであるGreasemonkeyでユーザスクリプトを作れば、クライアント側(つまり自分のブラウザ)で、Webサイトを改造できるということで挑戦してみた。
調べみると、どうも大多数のGreasemonkeyユーザは、既存の便利なユーザスクリプトを使うだけのようで、ユーザスクリプトの作り方についての情報が少ないので、ポインタ主体だがまとめておく。
- とりあえず読むべし(英語)。Dive Into Greasemonkey。 タイトルの通り、他の言語・環境でプログラムしたことはあるんだけど、ユーザスクリプトとかJavascriptとか、書いたことがない、という人にはベストな入門文書。PDFでダウンロードできるので便利。
- あとは、自分のやりたいことと似ているユーザスクリプトをGoogleででも探して眺める。自分は、ページ上にボタンを追加して思う通りの動作を追加させたかったので、Gmail Special Searchesがとても参考になった。
このあたりを準備できたら、まずは改造したいWebページの、改造したい辺りのDOMを、Firebugをインストールすると出てくる"Inspect Element"というメニューでながめる。idはどうなっているか、DOMの親子関係はどうなっているか、などなど。
そうしつつ、Dive Into Greasemonkeyを見ながら、とりあえずボタンを表示させてみるなどしつつ、格闘していけばそのうちできる。自分の場合は、Javascriptの開発経験もなかったため、週末を1日ほどつぶしてしまったが、その経験があればもっと短時間で済むはず。
ユーザスクリプトを作っている途中で気づいたことをメモ。
- Greasemonkeyで作成したユーザスクリプトは、ページ読み込み時に1回実行されるだけである。そのため、ユーザスクリプトで定義したfunctionもその場限りで消えてしまい、自分が追加したボタンでページ表示後に呼び出すことは、そのままではできない。addEventListenerを使う必要がある。具体的な使い方は、Gmail Special Searchesが参考になった。
- Javascriptには、sprintfがない。具体的には、数値を"02"などのように常に固定桁数(この場合2桁)で表示させたかったので、ちょっと困った。色々と検索したら、下記のように解決できることが分かった。なるほど。
str = ("00" + intVal).substr(-2);
Emacsで語数を数える
Emacsには、行数を数えるcount-lines-regionはあるが、単語数を数える機能は用意されていない。
調べると、"An Introduction to Programming in Emacs Lisp"でサンプルとして、count-words-regionという関数が見つかった。
http://www.gnu.org/software/emacs/emacs-lisp-intro/html_node/count-words-region.html
これは1語ごとに再帰呼び出しを使っており、ちょっと語数が多くなると、max-lisp-eval-depthの制限にひっかかってしまう。設定を変えるのもひとつのテだが、再帰を使わないで適当に書き直してみた(元のcount-words-regionを変更せずに使っているため、recursiveという名前が残っているが)。
(defun recursive-count-words (region-end) (let ((count 0)) (while (and (< (point) region-end) (re-search-forward "\\w+\\W*" region-end t)) (setq count (+ count 1)) ) count ) ) (defun count-words-region (beginning end) "Print number of words in the region. Words are defined as at least one word-constituent character followed by at least one character that is not a word-constituent. The buffer's syntax table determines which characters these are." (interactive "r") (message "Counting words in region ... ") (save-excursion (goto-char beginning) (let ((count (recursive-count-words end))) (cond ((zerop count) (message "The region does NOT have any words.")) ((= 1 count) (message "The region has 1 word.")) (t (message "The region has %d words." count))))))