pythonによるラジオ番組表の取得

現在のラジオ番組を表示させるプログラムをpython3で作成した。

GUIはwxpythonで、lxmlパーサーとラジコの番組表を受取る仕組みを利用した。環境はwindows8,python3.6で、開発環境はVsual Studio Code(バージョン1.17.0)

http://radiko.jp/v2/api/program/station/weekly?station_id="番組名"を入れると番組表がxmlにて取得できる。
lxmlパーサーでxpathを利用して要素ごとに読み込み、ラジオ曲と番組表を表示した。
25時からの番組があるためdatetimeを利用して日付のまたぎを考慮した。日付の差分はdatetime.timedelta(days=1)対応できた。（参考
[Python datetime 日付の計算、文字列から変換【決定版】](https://qiita.com/7110/items/4ece0ce9be0ce910ee90)）
Bangumiクラスとした。
Class Bangumi

- def __init__
 - 日付取得
 - 初期変数
 - 引数を取らない場合は東京の番組全部
- def get()
 - 番組を一日分表示
- def now()
 - 現在の番組を表示
- def now_next()
 - 現在と次の番組を表示

```python:radio_bangumi.py
#
#ラジオ番組表取得
#Copyright (C) BURAO MIMA
#2017/10/07
#xmlの構造の変化により、<station>の枠がある。それから,<progs>となっていく。
#stationsをstationにそしてprogsからprogと落とし込んでいく
#一気にとるのは無理なので番組ごとに変更した
#Bangumiをクラス化した
#
#

import lxml.etree
import sys,codecs,io
import datetime,locale


class Bangumi:
	def __init__(self, bangumi_mei=["TBS","QRR","LFR","INT","FMT","FMJ","JOAK","JOAB"]):   #東京の番組全部
		self.d = datetime.datetime.today()
		self.bangumi = []
		self.bangumi_name = ""
		if( bangumi_mei != 0 ):
			self.bangumi_ichiran = bangumi_mei
				
	def get( self ):
		for bangumi in self.bangumi_ichiran:
			root = lxml.etree.parse("http://radiko.jp/v2/api/program/station/weekly?station_id="+bangumi)
			print( root.xpath(".//name")[0].text)
			progl = root.xpath(".//progs")
			for progs in progl:
				date = progs.xpath(".//date")  # YYYYMMDD
				print ( date[0].text )
				for prog in progs.xpath(".//prog"):
					title = prog.xpath(".//title")[0].text
					stime = prog.get("ftl")  # HHMM
					etime = prog.get("tol")  # HHMM
					print ("\t" + stime[0:2] + ":" + stime[2:4] +
          					" ~ " + etime[0:2] + ":" + etime[2:4] +
         					"\t" + title)

	def now( self ):  #一日を分に直して現番組を検索
		now_time = (self.d.hour * 60) + self.d.minute  #現時刻の分直し
		if( (0 <= self.d.hour) and ( self.d.hour < 5) ):                  #24～29時の対応
			self.d = self.d - datetime.timedelta(days=1)              #日付を一日戻す
			now_time = (self.d.hour * 60) + self.d.minute + (24 * 60)
		for bangumi in self.bangumi_ichiran:
			root = lxml.etree.parse("http://radiko.jp/v2/api/program/station/weekly?station_id="+bangumi)
			#print( root.xpath(".//name")[0].text)
			progl = root.xpath(".//progs")
			for progs in progl:
				date = progs.xpath(".//date")  # YYYYMMDD
				if( self.d.strftime("%Y%m%d") == date[0].text ):
					for prog in progs.xpath(".//prog"):
						title = prog.xpath(".//title")[0].text
						stime = prog.get("ftl")  # HHMM
						etime = prog.get("tol")  # HHMM
						start_time = (int(stime[0:2]) * 60) + int(stime[2:4]) #番組開始の分直し
						end_time   = (int(etime[0:2]) * 60) + int(etime[2:4]) #番組終了の分直し
						if( (start_time <= now_time) and ( now_time < end_time) ):
							self.bangumi.append( stime[0:2] + ":" + stime[2:4] +
          							" ~ " + etime[0:2] + ":" + etime[2:4] +
         							"\t" + title)

	def now_next( self ):  #def 現在と次の番組
		now_time = (self.d.hour * 60) + self.d.minute  #現時刻の分直し
		if( (0 <= self.d.hour) and ( self.d.hour < 5) ):                  #24～29時の対応
			self.d = self.d - datetime.timedelta(days=1)              #日付を一日戻す
			now_time = (self.d.hour * 60) + self.d.minute + (24 * 60)
		for bangumi in self.bangumi_ichiran:
			root = lxml.etree.parse("http://radiko.jp/v2/api/program/station/weekly?station_id="+bangumi)
			next_flag = 0  #現番組発見のフラグ
			self.bangumi_name = root.xpath(".//name")[0].text
			progl = root.xpath(".//progs")
			for progs in progl:
				date = progs.xpath(".//date")  # YYYYMMDD
				if( self.d.strftime("%Y%m%d") == date[0].text ):
					for prog in progs.xpath(".//prog"):
						title = prog.xpath(".//title")[0].text
						stime = prog.get("ftl")  # HHMM
						etime = prog.get("tol")  # HHMM
						start_time = (int(stime[0:2]) * 60) + int(stime[2:4]) #番組開始の分直し
						end_time   = (int(etime[0:2]) * 60) + int(etime[2:4]) #番組終了の分直し
						if( next_flag == 1):  #現在の番組が見つかった場合は次の読み込む
    							self.bangumi.append( stime[0:2] + ":" + stime[2:4] +
          							" ~ " + etime[0:2] + ":" + etime[2:4] +
         							"\t" + title)
    							break
						if( (start_time <= now_time) and ( now_time < end_time) ):
							self.bangumi.append( self.bangumi_name + "\n" + stime[0:2] + ":" + stime[2:4] +
          							" ~ " + etime[0:2] + ":" + etime[2:4] +
         							"\t" + title)
							next_flag = 1							
						
if __name__ == "__main__":
	main = Bangumi()
	main.now_next()
	print (main.bangumi)
```

windows表示させたいのと、ラジオボタンで番組を選択したかったのでwx_radio_bangumi.pyw(コンソールを表示させたくなかった)

- ラジオボタンで番組選択
- TEXTCtrlに番組内容（現在と次の番組）

wx_radio_bangumiではBangumiクラスの内now_next()の関数しか使わない。

```python:wx_radio_bangumi.pyw
# -*- coding: utf-8 -*-
#ラジオ番組の現在の番組表をGUI表示する
#Copyright (C) BURAO MIMA
#2017/10/09

import wx
import radiko_bangumi

def selected_radiobutton(event):
    if event.GetId() == 1111:
        radio = radiko_bangumi.Bangumi(["TBS"])
        #frame.SetStatusText(radio.now())
    elif event.GetId() == 2222:
        radio = radiko_bangumi.Bangumi(["QRR"])
        #frame.SetStatusText(radio.now())
    elif event.GetId() == 3333:
        radio = radiko_bangumi.Bangumi(["LFR"])
        #frame.SetStatusText(radio.now())
    elif event.GetId() == 4444:
        radio = radiko_bangumi.Bangumi(["FMT"])
        #frame.SetStatusText(radio.now())
    elif event.GetId() == 5555:
        radio = radiko_bangumi.Bangumi(["INT"])
        #frame.SetStatusText(radio.now())
    elif event.GetId() == 9999:
        radio = radiko_bangumi.Bangumi()
    radio_text.Clear()
    radio_text.AppendText("ただいま読み込み中・・・")
    radio.now_next()
    radio_text.Clear()
    for bangumi_now in radio.bangumi:
        radio_text.AppendText(bangumi_now + "\n")

application = wx.App()
frame = wx.Frame(None, wx.ID_ANY, "ラジオ番組表", size=(500,250))
frame.CreateStatusBar()

panel = wx.Panel(frame, wx.ID_ANY)
panel.SetBackgroundColour("#AFAFAF")
 
radiobutton_parts = (wx.RadioButton(panel, 1111, "TBSラジオ"), wx.RadioButton(panel, 2222, "文化放送"), wx.RadioButton(panel, 3333, "ニッポン放送"), wx.RadioButton(panel, 4444, "TOKYO FM"),wx.RadioButton(panel, 5555, "Inter FM"), wx.RadioButton(panel, 9999, "全局"))
 
for radiobutton in radiobutton_parts:
    frame.Bind(wx.EVT_RADIOBUTTON, selected_radiobutton, radiobutton)   

radio_text = wx.TextCtrl(panel, wx.ID_ANY, style=wx.TE_MULTILINE)
font = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
radio_text.SetFont(font)

layout = wx.BoxSizer(wx.VERTICAL)
for radiobutton in radiobutton_parts:
    layout.Add(radiobutton, flag=wx.GROW)
layout.Add(radio_text, flag=wx.GROW)

panel.SetSizer(layout)

frame.Show()
application.MainLoop()
```

今回のプログラムにおいては、久々でありpythonの再勉強と開発環境の進歩を実感した。
Visual StudioCodeにてデバックモードとpythonのモードを途中から利用できるようにアップデートされたので開発が楽になった。特に補完機能はありがたい。python3のおかげで、Unicodeの問題から解放されたのは大きい。
失敗としては、wxpythonのインストールがAnacondaと重複してしましい。pip3とpipでwxpythonのインストールが上手くいかなかった。

ラジオは摘まみを回して聞くのが気に入っているのだが、いかんせん番組表がないのが辛いかった。androidのラジコは起動が遅くて番組が直ぐに確認できない不満もあったがかといって新聞はない。
InterFMまでしか対応してないのはよく聞く局だけ表示できるようにした。また東京のみであり、私的な利用に留まる。radikoで聞けばいい話である。（笑）