ZaurusでCronInetとMewによるメールの定期巡回
CronInetという、電源を落としていても定期的にネットにつないでコマンドを実行してくれる便利なZaurus用スクリプトがある。モバイルの可能性を広げてくれる、素晴らしいものだ。
fetchmailと連携して使う例しか紹介されてないけど、fetchmailはモバイル向きじゃない。理由はメールのサイズの閾値を設定できても、はじくことしかできない(ようだ)からだ。
はじかれたメールの存在をMUAから簡単に知ることはできない。
(いや、手はいくらでもあるだろうけど、めんどくさい)
だからといって、10MBとか普通に添付してくるメールを受け取るのも現実的でない。
添付を落とすような転送をかけて、ワンクッション置けばいいんだけどね。
モバイル的には、メールの先頭だけ受信して、後で用があらば選択受信したい。
検索という鬼門はあるものの、モバイルにおけるMUAは、それができるMewが最有力。
Mewの弱点は、Emacsの中で動くという性質上、外部からのキックでメールを受信させることが難しいことだ。
ゆえに、CronInetなどというヨダレもののスクリプトがあっても眺めているしか無かった。しかし、ElispやEmacsへの理解が進んで壁を越えることができたので書いておこう。
実現のキーはemacsclient
実現のキーになるのは、emacsclient。emacsclientは、起動中のEmacsにTCP/IP経由で引数を渡すことができるコマンドだ。
emacsclientを使うには、.emacs.elに
(server-start)
と書いておく。emacsclientは-eオプションでEmacsにElispを評価させることができる。これでMewのメール受信関数をキックしてやる、というのがアイデアの骨格になる。つまり、Emacsの外からメール受信を開始させられるのだ。
以下に紹介するスクリプト「mew-cron-retrieve.sh」をfetchmailの代わりにCronInetで定期実行してやると、Mewでメールの送受信ができる。
Emacs側の準備
以下を.emacs.el等に追加する。
;;emacsclientを使えるようにする
(server-start)
;;mew-cron-retrieve
(defun mew-cron-retrieve()
""
(interactive)
(defvar mew-cron-retrieve nil)
(defvar mew-cron-busy nil)
(if mew-cron-retrieve
(progn
(setq mew-cron-busy t)
(mew-summary-retrieve)
)
nil)
)
(add-hook 'mew-init-hook
(function
(lambda ()
(setq mew-cron-retrieve t)
)))
(add-hook 'mew-quit-hook
(function
(lambda ()
(setq mew-cron-retrieve nil)
)))
(add-hook 'mew-pop-sentinel-hook
(function
(lambda ()
(setq mew-cron-busy nil)
)))
(add-hook 'mew-send-hook
(function
(lambda ()
(setq mew-cron-busy t)
)))
(add-hook 'mew-smtp-flush-hook
(function
(lambda ()
(setq mew-cron-busy t)
)))
(add-hook 'mew-smtp-sentinel-hook
(function
(lambda ()
(setq mew-cron-busy nil)
)))
Mewのメール送受信は非同期処理なので、メール送受信を指示しても、送受信終了を待たずに制御が戻ってしまう。そのまま事後処理に入ってメールの送受信中にPPPを切断してしまうことが無いよう、add-hookして、処理中を示すフラグを上げ下げしている。
pop開始時のhookが無かったのでhookを追加しようとしたが、これはemacsclientからのキック時にやってやればいいことなので、run-hook追加はやめた。
CronInetからキックするシェルスクリプト
自分はmew-cron-retrieve.shという名前にしている。
これをCronInetでキックする。
#!/bin/sh
export LD_LIBRARY_PATH=/home/QtPalmtop/lib
export QPEGROUP=qpe
export QTDIR=/home/QtPalmtop
export QPEDIR=/home/QtPalmtop
export QPEUSER=zaurus
logfile="/home/zaurus/mew-kicked-log.txt"
ps x | grep -e ' emac[s]' >/dev/null
if [ $? ] ; then
echo "emacs found."
emacsclient -e '()' >/dev/null 2>&1
if [ "X$?" = "X0" ] ; then
echo "emacs server found."
emacsclient -e '(mew-cron-retrieve)'
echo "checking..."
while [ "X`emacsclient -e 'mew-cron-busy'`" = "Xt" ]; do
sleep 1
# debug print
# emacsclient -e 'mew-cron-busy'
done
echo "done."
echo "kicked at `date '+%Y-%m-%d %H:%M:%S'`" >> $logfile
echo "" | sudo ntpdate eric.nc.u-tokyo.ac.jp >> $logfile
fi
fi
sleep 5
while ifconfig ppp0 >/dev/null 2>&1; do
netctl -d >/dev/null 2>&1
sleep 5
done
Emacs内の変数mew-cron-busyを見て、それがtの間は「待ち」としている。
CronInet側での切断がうまく動かないので、このスクリプト内で切断までやってしまっている。
ログを取ったり、時刻合わせをしたりは余計かもしれないので、要らない人は削除してね。
少々不安定なことがあるものの、勝手にスリープから起きてPPP接続、メール巡回、ntpdateによる時刻合わせ、PPP切断、スリープ移行、という一連の動作ができ、常に最新のメールがMewのInboxに入っているので、Zaurusの利用価値が倍増した。
Mewの受信件数読み上げ
ついでに受信件数読み上げというのもやってみた。
自分は音声合成が大好きだ。ボコーダーとかも好き。
だから、やらねばなるまい。
zaurusで動作実績のある音声合成ソフトが二つある。
一つはflite。もう一つはMBROLA。
↓flite
http://www.speech.cs.cmu.edu/flite/download.html
↓MBROLA本体(音素ファイルとかは別途必要)
http://tcts.fpms.ac.be/synthesis/mbrola/bin/pocketlinux/mbr301h.zip
後者は日本語まで喋れる上に品質が高いものの、専用の発音表記のマクロを書かなければならず、お手軽じゃない。前段にhuman readableなテキストからマクロを生成する別のソフトが必要。また、再生には生成したデータをsoxで変換し、/dev/dspに流し込んでやる必要があるなど、何かと面倒。それにちょっと重い。
一方のfliteは扱いがとても簡単。
flite -t "Speech test."
これだけ。
というわけで、fliteを使ってMewで受信したメールの件数を読み上げる、というのをやってみた。整数を引数に取り、読み上げる関数を書いた。これを.emacs.elとかに書いておく。
1通以上だと"mails"になるという小技入り。
(defun read-off-number-of-retrieved-mail (n)
""
(interactive)
(cond ((= n 1)
(setq read-off-number-of-retrieved-mail-postfix " mail.\""))
((> n 1)
(setq read-off-number-of-retrieved-mail-postfix " mails.\""))
(t
(setq read-off-number-of-retrieved-mail-postfix " mail.\""))
)
(call-process-shell-command
(concat
"flite -t \"You got "
(int-to-string n)
read-off-number-of-retrieved-mail-postfix
))
)
でもって、mew-pop.elのmew-pop-sentinelの中に適宜この関数を挿入する。
書き換えたのが以下(Mew 4.2.52)。
僕は書き換えたので上書きしてしまった。
rttlという変数に、受信件数が整数型で入っている。
(defun mew-pop-sentinel (process event)
(let* ((pnm (process-name process))
(directive (mew-pop-get-directive pnm))
(mdb (mew-pop-get-mdb pnm))
(sshpro (mew-pop-get-ssh-process pnm))
(sslpro (mew-pop-get-ssl-process pnm))
(rttl (mew-pop-get-rttl pnm))
(dttl (mew-pop-get-dttl pnm))
(left (mew-pop-get-left pnm))
(bnm (or (mew-pop-get-bnm pnm) (current-buffer)))
(flush (mew-pop-get-flush pnm))
(kils (mew-pop-get-kils pnm))
(hlds (mew-pop-get-hlds pnm))
(uidl (mew-pop-get-uidl pnm))
(done (mew-pop-get-done pnm))
(error (mew-pop-get-error pnm))
(file (mew-expand-folder bnm mew-pop-msgid-file))
(buf (process-buffer process))
(thread-info (mew-pop-get-thread-info pnm)))
(mew-pop-debug "POP SENTINEL" event)
(set-process-buffer process nil)
(set-buffer bnm)
(mew-summary-mark-recover mdb)
(mew-remove-buffer buf)
(if (not done)
(let* ((rtrs (mew-pop-get-rtrs pnm))
(lefts (length rtrs))
(uid (nth 1 (car rtrs)))
recovered)
(mew-pop-message pnm "POP connection is lost")
(when (mew-pop-get-dispatched pnm)
(cond
((eq directive 'scan)
;; uidl is reversed.
(setq uidl (cdr (member uid uidl)))
(mew-lisp-save file uidl nil 'unlimit)
(setq recovered t))
((eq directive 'inc)
;; uidl is reversed.
(setq uid (assoc uid uidl))
(setq uidl (cdr (member uid uidl)))
(mew-net-uidl-db-set (mew-pop-passtag pnm) uidl)
(setq recovered t)))
(when recovered
(mew-pop-message
pnm
"%d message retrieved. %d messages are left due to an error"
(- rttl lefts) lefts)
(mew-summary-folder-cache-save))))
(if thread-info (mew-summary-retrieve-message-for-thread thread-info))
(cond
(error
;; retain the error message
)
((eq directive 'biff)
(funcall mew-biff-function rttl))
((eq directive 'sync)
(mew-pop-message pnm "Synchronizing messages...")
(mew-net-folder-sync bnm hlds)
(mew-pop-message pnm "Synchronizing messages...done"))
((eq directive 'inc)
(mew-biff-clear)
(mew-net-uidl-db-set (mew-pop-passtag pnm) uidl)
(cond
((= rttl 0)
(mew-pop-message2 pnm "No new messages" left))
((= rttl 1)
(mew-pop-message2 pnm "1 message retrieved" left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))
(t
(mew-pop-message2 pnm (format "%d messages retrieved" rttl) left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))))
((eq directive 'get)
(cond
((= rttl 0)
(mew-pop-message2 pnm "No new messages" left))
((= rttl 1)
(mew-pop-message2 pnm "1 message retrieved" left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))
(t
(mew-pop-message2 pnm (format "%d messages retrieved" rttl) left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))))
((eq directive 'scan)
(mew-biff-clear)
(cond
((= rttl 0)
(mew-pop-message pnm "No messages scanned"))
((= rttl 1)
(mew-pop-message pnm "1 message scanned")
(mew-lisp-save file uidl nil 'unlimit)
(mew-summary-folder-cache-save))
(t
(mew-pop-message pnm "%d messages scanned" rttl)
(mew-lisp-save file uidl nil 'unlimit)
(mew-summary-folder-cache-save))))
((eq directive 'exec)
(when kils
(mew-mark-exec-unlink bnm kils)
(mew-mark-kill-invisible)
(mew-summary-folder-cache-save))
(cond
((= dttl 0)
(mew-pop-message pnm "No messages deleted"))
((= dttl 1)
(mew-pop-message pnm "1 message deleted"))
(t
(mew-pop-message pnm "%d messages deleted" dttl))))))
;;
(mew-net-status-clear (mew-pop-get-status-buf pnm))
(mew-info-clean-up pnm)
(set-buffer-modified-p nil)
(mew-summary-unlock)
(if (and (processp sshpro) (not mew-ssh-keep-connection))
(process-send-string sshpro "exit\n"))
(if (and (processp sslpro) (not mew-ssl-keep-connection))
(delete-process sslpro))
(unless (eq directive 'biff)
(run-hooks 'mew-pop-sentinel-non-biff-hook))
(run-hooks 'mew-pop-sentinel-hook)
(when (and mew-auto-flush-queue flush)
(mew-smtp-flush-queue mew-case))))
これでメールを自動巡回して、メールがあったら受信件数読み上げ、というアレゲな環境が完成。
本当は、音楽を聞いているところにメールが届いたら、一時的に音楽再生を中止して、音声合成でお知らせ、読み上げが終わったら音楽再生再開、というのをやろうと思ったけど、標準搭載のプレイヤーはリモコンできない。madplayやmpg123なんかにシグナル送って一時停止ってのはできないだろうか。
MPDというのがあって、これをZaurusで動かしている人も居るみたいだけど、Mac OS Xで動かしてみたら負荷がかかると音が途切れ途切れ。Zaurusで大丈夫なのかな。
« 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 | Main | デジカメRAWファイル現像コマンドdcraw 8.37とliblcms 1.15のZaurus向けパッケージ »


Comments
Mew の検索ですが、Zaurus上でも十分実用になりますよ。
Hyper Estraier との組合せで利用しています。
Posted by: 通りすがり | 2006.09.13 at 01:14 AM
ご教授ありがとうございます。
早速FocVさんとこ行ってきます。
Posted by: moyashi | 2006.09.14 at 01:55 AM
こんにちわ、有益な情報の掲載ありがとうございます。
とても興味深く拝見させていただいています。
zaurus上でMBROLAは試されたのでしょうか?
festivalとかいうプリプロセッサと組み合わせるといいみたいなのですが、festivalのarmバイナリが探せませんでした。
このあたり情報お持ちでしたら、ご教示いただければ幸いです。
#fliteと比較してどれくらい品質が高い?かが特に知りたいです。まあ、小さい音声データなので、差は無いようにも思いますが。phoファイルの作りこみ次第というところでしょうか?
Posted by: 質問 | 2007.02.12 at 05:22 AM
こんにちは。MBROLAはZaurus上でも試しました。
音素ファイルにサンプルが付いていますので、それをレンダリング(?)させただけですが。
内容は、不思議の国のアリスの一節を読み上げるというものです。
Zaurus上で生成したものも、公式サイトに上がっているサンプルと同じ質です。
festivalはDebianにarmのバイナリがありました。
依存しているライブラリをたどってインストールすれば動くと思います。DebianをZaurusで動かすこともできるようですので、Debianさえ動いてしまえば、apt-getでインストールできるはずです。
ちょっと面倒なので僕はやりませんが……。
http://packages.debian.org/stable/sound/festival
依存しているライブラリの調査は、
/lib/ld-linux.so.2 --list /opt/QtPalmtop/bin/hogehoge
/lib/ld-linux.so.2 --list /opt/QtPalmtop/lib/hogehoge
でできます。
festivalというものが(今も)MBROLAをバックエンドとしたものなのかどうか、というのがよく分かっていません。
仮にfestivalがMBROLAのプリプロセッサだとして、それが自然言語をどの程度解析してMBROLAのマクロに展開してくれるのか、ということについても、もちろん分かりません。fliteで満足してしまったので、MBROLAについてはサンプルを試すだけでそれ以上は追求しませんでした。
phoファイルを作り込めばかなり自然な発声になるというのは、MBROLA公式サイトのサンプルを聴いて分かる通りです。fliteとの差は歴然です。
Debianから持ってきたRubyの拡張ライブラリであるkakasi.soが動いたのと、Rubyが分かるようになってきたので、MBROLA + MuDaTTSでの日本語読み上げには再挑戦したいと思っています。ただ、MuDaTTSでの日本語読み上げの品質は、MBROLAのサンプルから想像されるようなものではありません。
Posted by: moyashi | 2007.02.12 at 11:55 PM
お教えいただき、ありがとうございます。
festivalのインストールチャレンジしてみました。
が、どうしてもlibc.s0.6が/lib/ld-linx.so.2を見にいくようで、
/lib/は書き替え不可なので、/usr/lib/にインストールしたものを見に行くように、LD_LIBRARY_PATHで順序を変えてもだめです。
こうなっては手詰まりですよね?
zmuDaTTSの書き込みを横目で見つつ、どうしても英語の音声合成が試してみたく、festivalをインストールしたかったのですが。
Posted by: 質問 | 2007.02.23 at 09:11 PM
コメントありがとうございます。
この方も難渋してますね。
http://www.users.on.net/~hluc/myZaurus/custom.html
Speech Synthesisの項を見ると、mbrolaが難しいのでやはりfliteを選択してらっしゃいます。
FreeTTSというfestivalとは別のプリプロセッサはJava1.4以降のvmが必要で、1.3までのZaurusでは動かない。バックポートに期待しようにも、1.4以降のAPIに依存している部分が多いので、難しいだろうと。festivalは考えなかったんでしょうかね。
僕の100万倍はスキルのありそうな人が選択しなかったことには、何か理由があるのではないかと思ってしまいます。
ライブラリのパスについても、正直よく分かっていないんですよね。
万年スクリプターなもので。
festivalのコンパイルを試してみたいですが、やり残したものが積み上がっている状態なので、期待しないで待っていてください。
私のスキルは、こないだまでroundを使うのに-lmが必要、というのを知らなかったレベルですので!
SL-C3200だと標準で英文読み上げソフト付いてますしねえ……。
DebianはQtと共存できますし、何でもapt-getでバコバコ入るみたいですし、Dependsを勝手に入れてくれるので、とりあえず試すというレベルなら楽勝なのでは?
まあ「chrootしなきゃいけない」というのは常用を考える際に大きな壁になるかもしれませんが。
http://www.naismith.sakura.ne.jp/wiki/wiki.cgi?SL-C3000%2FFullDebian
Posted by: moyashi | 2007.02.23 at 11:12 PM