玩式草子─ソフトウェアとたわむれる日々

第37回「らじる☆らじる」再び

日中はまだまだ厳しい残暑が続き、行く夏を惜しむかのように蝉たちが頑張っているものの、朝晩はめっきりと涼しくなって秋の虫たちの合唱も聞こえるようになりました。熱帯夜が続く間は手を付ける気力が出なかった作業にも、少しずつ手を出せるようになってきました。

そのような作業の際の便利なBGMに、昨秋よりNHKがやっているネットラジオ「らじる☆らじる」があります。Linuxで「らじる☆らじる」を聞いたり録音したりする方法については、この連載でも以前取りあげたことがあります。その際は基本となるシェルスクリプトしか解説できませんでしたが、記事の中で紹介したPlamo Linuxのサイトではそれら録音用のシェルスクリプトを生成するためのPythonスクリプトも紹介しておきました。

手元では、このPythonスクリプトを使っていろいろな番組を録音して楽しんでいましたが、予約したい番組が増えるにつれ、スクリプトの欠点が目に付いてきました。最近になって、ようやくその問題点を多少改善することができたので、今回はその事例を紹介してみましょう。

既存スクリプトの問題点

最近のLinux環境では「らじる☆らじる」を予約録音する際に必要なツール類はあらかじめ揃っており、それらをどう組み合わせるかがポイントになります。

以前の記事でも紹介したように、手元では、録音の部分はmplayerlameをシェルスクリプトで組み合わせ、そのスクリプトをatコマンドで指定した時刻に実行することで予約機能を実現しています。これら一連の作業を行うために書いたのがradiru_rec.pyというスクリプトです。

このスクリプトでは、予約録音に必要となるチャンネルや開始・終了時刻以外に、データを保存するファイル名を設定する-t オプションを受けつけ、必要なシェルスクリプトを作成してatのジョブとして登録します。

$ radiru_rec.py -t 'ラジオ英会話「スペシャル・ウィーク」_1' r2 8/27 6:45 7:00
channel:r2
begin time:08/27/2012 06:45
duration(m):15
title:ラジオ英会話「スペシャル・ウィーク」_1
at 06:45 08/27/2012 -f /home/kojima/radiru_scripts/18566
warning: commands will be executed using /bin/sh
job 102 at Mon Aug 27 06:45:00 2012

上記実行例に示すように、radiru_rec.pyスクリプトは、~/radiru_scripts/18566 といったシェルスクリプトを作って、そのシェルスクリプトを番組の開始時刻に実行するようにatに登録しています。

radiru_rec.pyは1つの番組を指定するには十分な機能を持っていますが、ラジオ第二の語学講座には月曜から金曜まで同じ時間帯で放送する,いわゆる「帯番組」が多く、それらを予約するためには引数をすこしずつ変えながら、このスクリプトを何回も実行してやる必要があります。

$ radiru_rec.py -t 'ラジオ英会話「スペシャル・ウィーク」_2' r2 8/28 6:45 7:00
$ radiru_rec.py -t 'ラジオ英会話「スペシャル・ウィーク」_3' r2 8/29 6:45 7:00
$ radiru_rec.py -t 'ラジオ英会話「スペシャル・ウィーク」_4' r2 8/30 6:45 7:00
....

もちろん、シェルの履歴(ヒストリ)機能を使えば、最低限の修正だけで次の日の予約も可能ですが、語学講座や教養番組をそれぞれ2、3種類ずつ聞こうとすると、週あたり20近くの番組を予約する必要があり、一々スクリプトを実行するのもかなり手間です。

また、何度もradiru_rec.pyを実行をしているうちに、日時やタイトルを間違って予約してしまうこともよくあります。間違って予約したジョブを削除するには、atqat -cコマンドで該当するスクリプトのジョブIDを調べた上でatrmコマンドで削除する必要があります。

$ atq
102    Mon Aug 27 06:45:00 2012 a kojima
103    Tue Aug 28 06:45:00 2012 a kojima
104    Wed Aug 29 06:45:00 2012 a kojima
105    Thu Aug 30 06:45:00 2012 a kojima

$ at -c 102
#!/bin/sh
# atrun uid=1000 gid=100
# mail kojima 0
umask 22
KDE_MULTIHEAD=false; export KDE_MULTIHEAD
...
#!/bin/sh
sleep 10
mkfifo /tmp/fifo_18566
file=/home/kojima/MP3/ラジオ英会話「スペシャル・ウィーク」_1-`date +"%F-%H-%M"`.mp3
...

$ atrm 102

この作業も結構手間なので、もう少し簡単に予約の確認や取り消しができないか考えてみることにしました。

radiru_rec.pyの再設計

ラジオ第二の語学講座は、通常、月曜から金曜まで同じ時間帯で放送されています。しかし、講座によっては月曜から水曜は基本編で木曜と金曜が応用編になっていたり、月曜と火曜が基本編で水曜から金曜までが実践編になっていることがあります。また、週末の土曜と日曜のみに放送される講座などもあります。

一方、⁠カルチャーラジオ」のような教養番組では、3ヵ月ほどを一単位として、ひとつのテーマを全13回に渡って同じ曜日の同じ時間帯で放送する、という構成が中心ですが、⁠古典購読」のように週に一度の放送が1年間続く、という番組もあります。

これらさまざまな番組構成に柔軟に対応するにはどのような方法が簡単かつ汎用的でしょう? 当初は、日付の指定方法を"Mon-Fri"や"Mon-Wed"のように帯番組用に拡張するような方法を考えてみましたが、曜日での指定は人間の感覚的にはわかりやすいものの、最終的に録音用のシェルスクリプトをatに登録する際には、曜日を「月/日」の形に落しこまないといけないので処理が面倒そうです。

「毎週水曜日の20:00」とか「月曜から金曜までの6:45」のような時刻指定にはcrontabから実行するという方法も可能でしょう。crontabを使えば、登録を解除するまでは指定した時刻にジョブが実行され続けるので、⁠1年間聞き続けたい」ような番組では録り忘れることがなくて便利だとは思うものの、⁠しばらく試し聞きしてみる」程度の番組までcrontabに登録するのはちょっと大袈裟な気がします。何より、指定した時刻にジョブを実行するatと定期的にジョブを繰り返し実行するcrontabでは考え方や設定方法が異なるので、既存スクリプトの修正程度では対応が難しそうです。

それらをあれこれ考えていくうちに、どうやら「毎日」「毎週」という2つの種類の繰り返しパターンを用意して、指定した回数だけ「毎日」「毎週」の予約を繰り返す、というのが一番簡単そうだ、という気がしてきました。

たとえば、月曜から金曜までの帯番組を録音するには、⁠6:45から15分間」の設定を、月曜を起点に「毎日」で5回繰り返せば実現できますし、月曜から水曜までの番組では繰り返しを3回にすればいいでしょう。また「水曜の20:30から21:00まで」「毎週」で4回繰り返せば、週に一度の放送をひと月分まとめて予約できそうです。

一方、このような繰り返し機能を追加すると、録音したファイル名を区別するために、ファイル名に連番を振るような機能も必要になりそうです。

このように整理した結果、新しいスクリプトには「繰り返し単位を毎日にする」⁠繰り返し単位を毎週にする」⁠繰り返し回数を指定する」⁠ファイル名に付加する連番(の開始番号)を指定する」という4つの機能を追加することにしました。

コーディング

基本的なアイデアはまとまったので、さっそく実際のコーディング作業にかかりました。

前節で考えた4つの新機能程度なら、radiru_rec.pyの基本構造まで手を入れなくても、オプション機能程度の位置づけで実装できそうです。そこで、以前から採用していたファイル名を指定するための引数と前節で考えた4つの新機能を指定するための引数を、以下のように割り当てることにしました。

  • 繰り返し単位を毎日にする:-d(--daily)
  • 繰り返し単位を毎週にする:-w(--weekly)
  • 繰り返し回数を指定する:-r(--repeat)
  • 録音するファイル名を指定する:-t(--title)
  • ファイル名に付加する連番を指定する:-n(--number)

このようなコマンドラインオプションを解析する機能は、C言語ではgetopt()という関数が引き受けており、Pythonにもそれと同等のgetoptというモジュールが用意されています。従来のスクリプトではこのgetoptモジュールを用いてオプションを解析していましたが、最近のPython(2.7以降)では、より強力なargparseというモジュールが用意されているそうなので、argparseモジュールの勉強がてら、コマンドラインオプションを解析する部分を書いてみました。

 1 #! /usr/bin/python
 2 # -*- coding: euc-jp -*-;
 3 
 4 import argparse
 5  
 6 def get_args():
 7     parser = argparse.ArgumentParser(description='らじる☆らじる録音スクリプトビルダー')
 8     parser.add_argument('-d','--daily', action='store_true')
 9     parser.add_argument('-w','--weekly', action='store_true')
10     parser.add_argument('-t', '--title')
11     parser.add_argument('-n', '--number')
12     parser.add_argument('-r', '--repeat')
13     parser.add_argument('rec_data', nargs='*')
14     return  parser.parse_args()
15 
16 def main():
17     params = get_args()
18 
19     print params
20 
21 if __name__ == "__main__":
22     main()

6行目からのget_args()がコマンドラインオプションの解析部です。

argparseモジュールでは、まずArgumentParser()メソッドでオプション解析用のパーサオブジェクト(parser)を作り、そのオブジェクトに、解釈すべき引数をadd_argument()メソッドで追加していきます。上記の例では、8行目から12行目が前節で考えたコマンドラインオプションを解析するための指定で、13行目ではオプション以外のデータ(チャンネルや録音開始時刻などの情報)rec_dataというインスタンスに一括保存するように指定しています。

argparseモジュールの詳細はPythonのオンラインドキュメント等に譲りますが、上記リスト中のaction='store_true'はその引数をフラグとして使う指定、13行目のnargs='*'の指定は、引数が取るパラメータ数の指定で、それまでに解析し残した全ての引数('*')をrec_dateというインスタンスに収めるように指示しています。

こうして作ったパーサオブジェクトでparse_args()メソッドを実行すれば、コマンドラインに与えられたオプション引数が解析され、解析した結果が返されます。

実際に、このスクリプトをargtest.pyという名前で保存して引数を与えてみると、このような結果になりました。

$ python argtest.py -t myprog -d -r5
Namespace(daily=True, number=None, rec_data=[], repeat='5', title='myprog', weekly=False)

$ python argtest.py -w -r5 -n10 -t myprog2 r2 8/28 6:45 15m
Namespace(daily=False, number='10', rec_data=['r2', '8/28', '6:45', '15m'], repeat='5', title='myprog2', weekly=True)

この例ではparamsオブジェクトの内容がまとめて表示されていますが、それぞれの引数はparams.dailyparams.numberとして参照できます。

argparseモジュールの面白いところは、CUIコマンドの標準的なヘルプメッセージを自動的に生成してくれる点です。上記のサンプルではヘルプメッセージを表示する-hオプションは設定していませんが、argparseモジュールではヘルプメッセージ機能はデフォルトで有効になっており、-hを指定すると以下のようなヘルプメッセージが自動的に生成、表示されます。

$ python argtest.py -h
usage: argtest.py [-h] [-d] [-w] [-t TITLE] [-n NUMBER] [-r REPEAT]
                  [rec_data [rec_data ...]]

らじる☆らじる 録音スクリプトビルダー

positional arguments:
  rec_data

optional arguments:
  -h, --help            show this help message and exit
  -d, --daily
  -w, --weekly
  -t TITLE, --title TITLE
  -n NUMBER, --number NUMBER
  -r REPEAT, --repeat REPEAT

各オプションの説明はadd_argument()メソッドの中で設定できますが、特に設定しなくても、どのようなオプションが指定可能かがわかる程度のヘルプメッセージは自動生成してくれるようです。

オプションの解析部やヘルプメッセージの表示といった結構手間のかかる処理がargparseモジュールで簡単に書けたので、調子に乗って残りの部分もざっと直してみました。スペースの都合上詳細は省きますが、今回の改造は「毎日」「毎週」の繰り返し機能を追加する程度なので、元のスクリプトのメイン部に新たなループ処理を追加する程度の変更で対応できました。

ざっと修正したスクリプトをradiru_rec2.pyという名前にして、動作テストをしてみました。

$ ./radiru_rec2.py -t 英会話タイムトライアル -n101 -d -r5 r2 9/3 8:30 10m
英会話タイムトライアル_101,r2, 2012-09-03 08:30:00,10
warning: commands will be executed using /bin/sh
job 61 at Mon Sep  3 08:30:00 2012
英会話タイムトライアル_102,r2, 2012-09-04 08:30:00,10
warning: commands will be executed using /bin/sh
....
英会話タイムトライアル_105,r2, 2012-09-07 08:30:00,10
warning: commands will be executed using /bin/sh
job 65 at Fri Sep  7 08:30:00 2012

$ ./radiru_rec2.py -t 日曜カルチャー「時代の音」 -n1 -w -r4 9/2 20:00 21:00
日曜カルチャー「時代の音」_1,r2, 2012-09-02 20:00:00,60
warning: commands will be executed using /bin/sh
job 74 at Sun Sep  2 20:00:00 2012
日曜カルチャー「時代の音」_2,r2, 2012-09-09 20:00:00,60
...
日曜カルチャー「時代の音」_4,r2, 2012-09-23 20:00:00,60
warning: commands will be executed using /bin/sh
job 77 at Sun Sep 23 20:00:00 2012

ざっと見る限りでは、日毎の繰り返しも週毎の繰り返しも正しく計算できているようです。

予約確認/削除スクリプト

最初にも紹介したように、radiru_rec2.pyで予約したジョブを削除するには、atqat -cでジョブのIDを確認し、atrmでそのジョブを削除することになります。また、~/radiru_scripts/以下に作成された録音用のシェルスクリプトも削除しておく方がいいでしょう。

これらの作業を1つのコマンドでまとめて行えるように、予約確認用のradiru_check.pyと予約削除用のradiru_del.pyというスクリプトも書いてみました。いずれのスクリプトもPythonから外部プロセスを実行するsubprocess.check_output()を使ってatqなどを実行している簡単なスクリプトですが、使ってみると案外便利です。

$ ./radiru_check.py 
61 r2 英会話タイムトライアル_101.mp3 9/3(月) 08:30:00 10m
62 r2 英会話タイムトライアル_102.mp3 9/4(火) 08:30:00 10m
...
72 r2 日曜カルチャー「時代の音」_3.mp3 9/17(月) 20:00:00 60m
73 r2 日曜カルチャー「時代の音」_4.mp3 9/24(月) 20:00:00 60m

$ ./radiru_del.py 61 62
target script:60
delete que_id:62 filename:/home/kojima/radiru_scripts/60
target script:61
delete que_id:63 filename:/home/kojima/radiru_scripts/61 

$ ./radiru_check.py 
63 r2 英会話タイムトライアル_103.mp3 9/5(水) 08:30:00 10m
64 r2 英会話タイムトライアル_104.mp3 9/6(木) 08:30:00 10m
...
72 r2 日曜カルチャー「時代の音」_3.mp3 9/17(月) 20:00:00 60m
73 r2 日曜カルチャー「時代の音」_4.mp3 9/24(月) 20:00:00 60m

これら3種のスクリプトで「らじる☆らじる」の録音予約や確認、予約解除がより簡単にできるようになりました。

今回紹介したスクリプト類はPlamo Linux wikiの筆者の日記のページに置いておきますので、試してみたい人は御自由にお使いください。

動作確認は手元のPlamo Linux環境でしか行っていませんが、Python-2.7系がある環境ならば、先頭部分の文字コード指定をUTF-8等に修正する程度で使えるのではないかと思います。


最近では、スマートフォンやタブレットPCの普及もあって、自分でコードを書く人はずいぶん少なくなったと思いますが、自分のニーズを自分で満すことができるのがOSSの醍醐味ですし、コードを書くためには自分の抱えている問題を整理、分析することが必要で、その結果、問題を異なる視点から見直すことになって、以前には気づかなかった答が見つかることもあります。

これからの季節、長い夜のコンパイル作業の友にこれらのスクリプトで録りためた番組が活躍してくれそうですが、録音した語学番組をBGM代りに聞き流しているだけでは、語学力が向上することは無さそうです(苦笑⁠⁠。

おすすめ記事

記事・ニュース一覧