初めに
前回までにvoicepeakをバックグラウンドで自動操作する関数を実装することはできました。
今回は、ニコニコ生放送のコメントをPythonで取得したり、私なりに使いやすくしようとした細々とした調整を紹介したりします。
前編はこちらです。
pythonでのニコ生のコメント取得
いきなりですが、コードを示したいと思います。
def nicoCommentGet():
### コメントを取得したい放送のURLを指定
live_id = "co5043209"
#live_id = "co3000390" # コミュニティIDを指定すると放送中のものを取ってきてくれる
url = "https://live2.nicovideo.jp/watch/"+live_id
### htmlを取ってきてWebSocket接続のための情報を取得
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
embedded_data = json.loads(soup.find('script', id='embedded-data')["data-props"])
urlSystem = embedded_data["site"]["relive"]["webSocketUrl"]
### websocketでセッションに送るメッセージ
message_system_1 = {"type":"startWatching",
"data":{"stream":{"quality":"abr",
"protocol":"hls",
"latency":"low",
"chasePlay":False},
"room":{"protocol":"webSocket",
"commentable":True},
"reconnect":False}}
message_system_2 ={"type":"getAkashic",
"data":{"chasePlay":False}}
message_system_1 = json.dumps(message_system_1)
message_system_2 = json.dumps(message_system_2)
### コメントセッション用のグローバル変数
uri_comment = None
message_comment = None
### 視聴セッションとのWebSocket接続関数
async def connect_WebSocket_system(urlSystem):
global uri_comment
global message_comment
### 視聴セッションとのWebSocket接続を開始
async with websockets.connect(urlSystem) as websocket:
### 最初のメッセージを送信
await websocket.send(message_system_1)
await websocket.send(message_system_2) # これ送らなくても動いてしまう??
print("SENT TO THE SYSTEM SERVER: ",message_system_1)
print("SENT TO THE SYSTEM SERVER: ",message_system_2)
### 視聴セッションとのWebSocket接続中ずっと実行
while True:
message = await websocket.recv()
message_dic = json.loads(message)
print("RESPONSE FROM THE SYSTEM SERVER: ",message_dic)
### コメントセッションへ接続するために必要な情報が送られてきたら抽出してグローバル変数へ代入
if(message_dic["type"]=="room"):
uri_comment = message_dic["data"]["messageServer"]["uri"]
threadID = message_dic["data"]["threadId"]
message_comment = [{"ping": {"content": "rs:0"}},
{"ping": {"content": "ps:0"}},
{"thread": {"thread": threadID,
"version": "20061206",
"user_id": "guest",
"res_from": -150,
"with_global": 1,
"scores": 1,
"nicoru": 0}},
{"ping": {"content": "pf:0"}},
{"ping": {"content": "rf:0"}}]
message_comment = json.dumps(message_comment)
### pingが送られてきたらpongとkeepseatを送り、視聴権を獲得し続ける
if(message_dic["type"]=="ping"):
pong = json.dumps({"type":"pong"})
keepSeat = json.dumps({"type":"keepSeat"})
await websocket.send(pong)
await websocket.send(keepSeat)
### コメントセッションとのWebSocket接続関数
async def connect_WebSocket_comment():
loop = asyncio.get_event_loop()
global uri_comment
global message_comment
### 視聴セッションがグローバル変数に代入するまで1秒待つ
await loop.run_in_executor(None, time.sleep, 1)
### コメントセッションとのWebSocket接続を開始
async with websockets.connect(uri_comment) as websocket:
### 最初のメッセージを送信
await websocket.send(message_comment)
print("SENT TO THE COMMENT SERVER: ",message_comment)
### コメントセッションとのWebSocket接続中ずっと実行
while True:
message = await websocket.recv()
message_dic = json.loads(message)
if "chat" in message_dic:
print(message_dic["chat"]["content"])
if (start):
while(speaking):
pass
sendVoicePeak(message_dic["chat"]["content"])
else:
print("false")
else:
print("RESPONSE FROM THE COMMENT SERVER: ",message_dic)
asyncio.new_event_loop().run_in_executor(None,wait)
### asyncioを用いて上で定義した2つのWebSocket実行関数を並列に実行する
loop = asyncio.get_event_loop()
gather = asyncio.gather(
connect_WebSocket_system(urlSystem),
connect_WebSocket_comment(),
)
loop.run_until_complete(gather)
このページを参考にさせていただきました。websocketに関する部分はこのページで紹介されているので、省略させて頂いて、元のソースコードから変更した部分を紹介したいと思います。
変更したのはWhile True
の中のみです。
### コメントセッションとのWebSocket接続中ずっと実行
while True:
message = await websocket.recv()
message_dic = json.loads(message)
if "chat" in message_dic:
print(message_dic["chat"]["content"])
if (start):
while(speaking):
pass
sendVoicePeak(message_dic["chat"]["content"])
else:
print("false")
else:
print("RESPONSE FROM THE COMMENT SERVER: ",message_dic)
message_dic["chat"]["content"]
にはコメントのみの情報が含まれています。なぜ、コメントのみの情報になるのかや、 if "chat" in message_dic
の処理をかませている理由が分からない方は、以下の記事をご覧ください。
その後、if (start):
という文がありますが、放送が始まって、時間が経過した後にコメントを取得すると、過去のコメントが一気に流れてきてしまい、邪神ちゃんが全部それを読み上げようとしてしまうので、プログラムを立ち上げてから5秒以内はstart
をfalse
にしておくプログラムを別で作りました。
start = False
def wait():
global start
time.sleep(5)
start = True
こうすることでwebsocketが開通し、一気にコメントが流れてくる時間の間は、start
がfalse
なので、邪神ちゃん読み上げプログラムが書かれているsendVoicePeak()
関数にコメントが流れて、邪神ちゃんに大量のコメントを送って、邪神ちゃんを困らせないようにしています。
そして、
while(speaking):
pass
sendVoicePeak(message_dic["chat"]["content"])
この部分のwhile(speaking):
ですが、これは邪神ちゃんがコメントを読み上げている間に次のコメントが入力されることを防止するためのものです。
前回紹介したSendVoicePeak関数に少し手を加えました。
speaking = False
def sendVoicePeak(message):
global speaking
speaking = True
message = replaceUrls(message)
#ウインドウハンドルを取得
VoicePeak = win32gui.FindWindow(None, "VOICEPEAK") #voicepeakのウインドウハンドルを取得
JUCE = win32gui.FindWindow(None,"JUCEWindow") #voicepeak(juce)のウインドウハンドルを取得
time.sleep(0.05)
#メッセージを入力
win32gui.SendMessage(JUCE, win32con.WM_ACTIVATE,2,0) #ウインドウをアクティブ化
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0) #テキストエリアにフォーカスする
for s in message:
print(s);
win32gui.SendMessage(VoicePeak, win32con.WM_CHAR,ord(s),0) #読み上げたい文字を送信
time.sleep(0.05)
win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0) #テキストエリアのフォーカスを外す
time.sleep(0.5)
#再生
win32gui.SendMessage(VoicePeak,win32con.WM_CHAR,32,0) #スペースを送信して、読み上げを実行
nowPlaying();
#入力した文字を削除
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0) #テキストエリアにフォーカスする
for s in message:
win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,0x27,0) #右矢印キーを入力
win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,8,0) #バックスペースを入力
time.sleep(0.05)
win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0)
speaking = False
関数の実行中はspeaking
をTrue
にすることで、コメント取得プログラムの中にある、while(speaking):
が関数が実行されているときだけ、無限ループして、SendVoicePeak関数が実行されないようにしています。
細かい調整
voicePeak起動時の調整
voicepeakが起動したとき、一度だけvoicePeakのテキストエリアをクリックしないと、なぜか読み上げプログラムが上手く動きませんでした。毎回起動したあと、クリックするのが面倒くさいので、win32apiの力を使って自動化しました。
def setupVoicePeak():
VoicePeak = win32gui.FindWindow(None, "VOICEPEAK") #voicepeakのウインドウハンドルを取得
rect = win32gui.GetWindowRect(VoicePeak)
nowPos = win32api.GetCursorPos()
win32api.SetCursorPos((rect[0]+161, rect[1]+167))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, rect[0]+161, rect[1]+167, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, rect[0]+161, rect[1]+167, 0, 0)
win32api.SetCursorPos(nowPos)
sendVoicePeak("起動しました")
関数実行時に邪神ちゃんが「起動しました」と言ってくれたら、立ち上げに成功しています。
ソースコード全体の紹介
こちらに載せています。ソースコードはかなり読みにくいですが、お許しを。
分からない点はこちらへ
毎日配信しているので、生放送のコメントで教えてもらえると対応がしやすいです。
(同時配信をしているので、どちらかお好きな方でコメントを)