SSブログ

LINEからラズパイ経由でGoogle Home(Nest Hub)のYouTube再生リストのキューに動画をどんどん追加する [Raspberry Pi]

やりたいこと
LINEのMessaging APIを通して、YouTubeのURLを送信してラズベリーパイのみを
経由して、Google Home(Nest Hub)のYouTubeプレイリストのキューに
再生する動画を順次追加する。

意地でも声入力で再生させたくない人向け

システムイメージ
00.PNG

動作の条件:Google Home(Nest Hub)でYouTubeの動画が再生中であること
※下記のスクリプトでは1個目を再生させられるので、まずこれを使うなどして
立ち上げる必要がある。

一応出来る限り1から説明していく。

前提
・Messaging APIのアカウントを作成していること(このあたりを参考に作成してください)
・Raspbian OSがインストールされていること
・ルーター側でIPのポートマッピングが出来ること

方針
・出来る限りフリーのサービスを用いる
・サーバーはすべてラズパイで完結させる(Herokuなどを使わない)

https://developers.line.biz/console/にアクセスし、Providersを新規作成
01.PNG

②適当な名前をつけて作成する
02.PNG

③Create a Messaging API channelを選択
03.PNG

④適宜必要事項を入力して、利用規約に同意しCreate
04.png

⑤作成したAPIのページに移動し、Messaging APIのタブに移動する
05.PNG

QRコードが表示されているので、スマホのLINEからQRで友だち追加しておく

⑥独自ドメインを取得する
既に取得している場合は、ドメイン関係の手順は飛ばして良い

LINEのwebhookサーバを設定するために、独自ドメインを取得する必要がある。

今回はフリーのmydns.jpを用いた手順で説明する
https://www.mydns.jp/?MENU=010にアクセスし、登録情報を入力してアカウント作成
06.PNG
IDとパスワードが登録したメールアドレスに送られてくるので、メモしておく

https://www.mydns.jp/にアクセスし、上記アカウントでログインする
07.PNG

⑧DOMEIN INFO(https://www.mydns.jp/?MENU=300)を開き、
 自分の好きなドメイン名を入力する
08.PNG
Hostnameの欄は*を入力しておく

⑨ラズパイを起動し、ラズパイのIPを固定にする
/etc/dhcpcd.confを開く

古いラズパイの場合
sudo leafpad /etc/dhcpcd.conf
leafpadがない場合
sudo mousepad /etc/dhcpcd.conf
09.PNG

⑩有線の場合
interface eth0の部分のコメントアウトを外し、以下のように入力
interface eth0
static ip_address=192.168.(環境に合わせる).(他端末にかぶらない適当な数字)/24
static routers=192.168.(環境に合わせる).1
static domain_name_servers=192.168.(環境に合わせる).1
10.PNG

⑪無線の場合
wlan0を追記する
interface wlan0
static ip_address=192.168.(環境に合わせる).(他端末にかぶらない適当な数字)/24
static routers=192.168.(環境に合わせる).1
static domain_name_servers=192.168.(環境に合わせる).1
11.PNG

ラズパイを一旦再起動し、上記IPになっているかを確認する

⑫mydnsに自分のリモートIPを通知する
ターミナルを開き、以下を入力
wget -O /dev/null http://mydnsの自分のID:mydnsのパスワード@www.mydns.jp/login.html
12.PNG
上記のように、200 OKが出ていれば成功

⑬mydnsにIPが通知されていることを確認する
13.PNG
上記の登録状況に0以外が入っていれば成功している

⑭mydnsにIPを定期通知するスクリプトを作成する
mydnsの場合、一定期間IP通知がないと使用されていないとみなされ、
アカウントが削除されるため定期的に通知するようにする必要がある。
これに関しては色々やり方があるが、今回はcrontabを用いることとする

/etc/cron.d/にshファイルを作成する(sudo mousepad /etc/cron.d/mydns.sh等)
内容は以下
#!/bin/sh
/usr/bin/wget -O /dev/null http://mydnsの自分のID:mydnsのパスワード@www.mydns.jp/login.html
保存したら実行権限をつける
sudo chmod 777 /etc/cron.d/mydns.sh

⑮crontabにIP定期通知するスクリプトを設定する
以下を実行
sudo crontab -e

14.PNG
初回時は上記のメッセージが出るが、どのエディタを使うかという問いなので、1が推奨

15.PNG
青い文字の画面が出るはずなので、↓キーでスクロールして以下の行を追記
0 */1 * * * /etc/cron.d/mydns.sh
簡単に言うと、1日毎に通知する(詳細はcrontabの使い方を調べてください)。

書いたらCtl+X→y→Enterで終了

⑯SSL証明書の取得
既に持っているなら飛ばして良い

LINEのwebhookサーバーはhttps化していないと動作しないので、
SSL証明書を導入する必要がある(自己証明書では×)

今回はフリーのLet's Encryptを用いる

以下コマンドを入力し、certbot-autoを取得する
wget https://dl.eff.org/certbot-auto
続けて以下のコマンドを入力する
sudo mv certbot-auto /usr/local/bin/certbot-auto
sudo chown root /usr/local/bin/certbot-auto
sudo chmod 0755 /usr/local/bin/certbot-auto
sudo /usr/local/bin/certbot-auto

以下の画面が出るので、yを押して完了まで待つ
16.PNG

⑰certbot-autoでSSL証明書ファイルを取得する
まず、ルーター側でラズパイのIPに対してポート80をマッピングする
(これをやってないと取得失敗する)

以下のコマンドを入力する
sudo /usr/local/bin/certbot-auto certonly --register-unsafely-without-email
17.PNG
上記のような画面が出るが、今回はラズパイ上で取得したいので1を入力

⑱初回入力時はサービスに同意するかを聞かれる
aを入力する
18.PNG

⑲ドメイン名を入力
mydnsで設定したドメイン名を入力する
19.PNG

⑳証明書が保存される
20.PNG
上記のような画面が出ていれば成功

㉑証明書をファイルの確認
/etc/letsencrypt/live/設定したドメイン.mydns.jp以下にfullchain.pemや
privkey.pemがあることを確認する
21.PNG

この場所にあると扱いにくいので、ホームディレクトリ等にコピーし、アクセス権を変更しておく
cp fullchain.pem /home/自分のアカウント
cp privkey.pem /home/自分のアカウント
chown -R 自分のアカウント:自分のアカウント /home/自分のアカウント/*.pem
chmod 777 /home/自分のアカウント/*.pem
これに関しても、mydns同様一定期間更新しないと無効な証明書となるので、
crontabに登録しておくと良い

以下のような内容を/etc/cron.d/certbot.shに作成する
#!/bin/sh
sudo /usr/local/certbot/certbot-auto renew
cd /etc/letsencrypt/live/設定したドメイン.mydns.jp
cp fullchain.pem /home/自分のアカウント
cp privkey.pem /home/自分のアカウント
chown -R 自分のアカウント:自分のアカウント /home/自分のアカウント/*.pem
chmod 777 /home/自分のアカウント/*.pem
sudo chmod 777 /etc/cron.d/certbot.sh

crontab -eで以下を追記する
0 0 1 * * /etc/cron.d/certbot.sh
㉒必要なモジュールをインストールする
まずターミナルから以下を実行してインストールする
pip3 install flask
sudo pip3 install catt
pip3 install casttube
更に、Google Home(Nest Hub)のスクリーンIDを取得する
※この後導入するcasttubeの仕様上、スクリーンIDを取得する必要がある

以下を実行し、IDを取得する
git clone https://github.com/ur1katz/CastTube-Scripts.git
cd CastTube-Scripts/Scripts/
python3 ./get_screen_id.py "自分のデバイス名(アプリなどから確認してください)"
22.PNG
ランダムのような文字列が出るはずなので、メモしておく

㉓LINEのアクセストークンを取得する
LINEのMessaging APIのページを再び開き、下の方にあるChannel access tokenの
Issueをクリックする
23.PNG
画像ではReissueになっているが、一度実行するとReissueとなる
表示された文字列(Token)をメモしておく

㉔ルーター側でラズパイのIPに対してwebhookサーバに用いるポート番号をマッピングする
1000番台以下のポートを用いる場合は、この後の手順をすべてsudoで
やる必要があるので、できれば10000番台以上65535以下の適当なポート番号が望ましい

㉕WebhookのURLを設定する
LINEのMessaging APIのページを再び開き、Webhook URLのEditを選択する
24.PNG
アドレスの部分に以下を入力
https://設定したドメイン.mydns.jp:上記で設定したポート番号/
25.PNG
Use webhookを有効にする
27.PNG

㉖webhookサーバーを立ち上げる
以下のpythonスクリプトを保存し、次の部分を自分の環境に合わせて変更する
fullchain_pathにfullchain.pemのフルパスを記載
prvkey_pathにprivkey.pemのパスを記載
server_portに設定したポート番号を記載
line_tokenにLINEのトークンを記載
device_nameにGoogle Home(Nest Hub)のデバイス名を記載
device_screen_idにGoogle Home(Nest Hub)のスクリーンIDを記載

import ssl
import subprocess
import requests
import json
import re
import urllib.parse
from flask import Flask, request, jsonify
from casttube import YouTubeSession


fullchain_path = 'fullchain.pemのフルパスを記載'
prvkey_path = 'privkey.pemのフルパスを記載'
server_port = 設定したポート番号を記載
line_token = 'LINEのトークンを記載'
device_name = 'Google Home(Nest Hub)のデバイス名を記載'
device_screen_id = 'Google Home(Nest Hub)のスクリーンIDを記載'


line_reply_api = 'https://api.line.me/v2/bot/message/reply'
youtube_long_url = 'https://www.youtube.com/watch?'
youtube_short_url = 'https://youtu.be/'


app = Flask(__name__)
session = YouTubeSession(device_screen_id)


@app.route('/', methods=['GET'])
def get_reply():
	return jsonify({}), 200

@app.route('/', methods=['POST'])
def post_reply():
	req_json = request.json['events'][0]
	reply_token = req_json['replyToken']
	req_message = req_json['message']['text']
	messages = req_message.split('\n')
	
	reply_message = ''
	
	try:
		if 'add' == messages[0] or 'next' == messages[0] or 'remove' == messages[0]:
			reply_message = messages[0] + '\n'
			lines = messages[1:]
			if 'next' == messages[0]:
				lines = reversed(lines)
			for line in lines:
				id = extract_id(line)
				if '' == id:
					reply_message += 'invalid url:'
				else:
					if 'add' == messages[0]:
						session.add_to_queue(id)
					elif 'next' == messages[0]:
						session.play_next(id)
					else:
						session.remove_video(id)
				reply_message += line + '\n'
			
			reply_message = reply_message.rstrip('\n')
		elif 'clear' == messages[0]:
			session.clear_playlist()
			reply_message = 'clear'
		else:
			id = extract_id(messages[0])
			if '' != id:
				cast_cmd = ['catt', '-d', device_name, 'cast', messages[0]]
				try:
					subprocess.check_output(cast_cmd, stderr=subprocess.STDOUT).decode()
					reply_message = 'cast\n' +  messages[0]
				except subprocess.CalledProcessError as e:
					reply_message = 'cast error\n' + messages[0] + '\n' + e.output.decode()
	
	except Exception as e:
		reply_message = str(e.args)
	
	if '' != reply_message:
		result = reply_text(reply_token, reply_message)
		if 200 != result.status_code:
			print('reply failed:' + str(result.status_code))
	return jsonify({}), 200

def extract_id(url):
	if re.match(youtube_long_url, url):
		return urllib.parse.parse_qs(urllib.parse.urlparse(url).query)['v'][0]
	if re.match(youtube_short_url, url):
		return url[len(youtube_short_url):len(youtube_short_url) + 11]
	return ''

def reply_text(reply_token, text):
	return requests.post(
		line_reply_api,
		data=json.dumps({
			'replyToken': reply_token,
			'messages': [
				{
				'type': 'text',
				'text': text
				}
			]
		}).encode('utf-8'),
		headers={
			'Content-Type': 'application/json; charset=UTF-8',
			'Authorization': 'Bearer ' + line_token
		}
	)

if __name__ == '__main__':
	context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
	context.load_cert_chain(fullchain_path, prvkey_path)
	
	print('start:' + str(server_port))
	app.run(host='0.0.0.0', port=server_port, ssl_context=context, threaded=True)
以下のようにコマンドを入力してサーバーを立ち上げる
python3 my_srv.py
㉗Webhookサーバーが使えるか確認
LINEのMessaging APIのページを再び開き、WebhookのURLのVerifyを選択する

26.PNG
上記のようなメッセージが出れば成功
出ない場合はサーバーが立ち上がっていない、ルーターのポートマッピングが失敗、
IPが固定できてないなどしている可能性が高いので、それぞれ設定できているかを確認する

㉘自動応答メッセージをオフにしておく
デフォルトのままだとメッセージ送信の度に固定メッセージが返って鬱陶しいので、
こないようにしておく

Auto-reply messagesのEditを選択する
28.PNG

基本設定>あいさつメッセージと、詳細設定>応答メッセージをオフにする
29.PNG

㉙LINEで追加したAPIのチャットでYouTubeのURLを送信し、Google Home(Nest Hub)が
 立ち上がることを確認する

上記スクリプトでは、URLのみを入力することで再生が始まる

30.png
cast 飛ばしたURLのメッセージが返ってこれば成功しているはず

㉚再生キューに動画を追加する
↑でYouTubeが再生されている間に入力することが条件

add
追加したいURL(複数行入力可)を入力することで、次再生する動画に追加される
31.png
add 送ったURLのメッセージが返ってこれば成功しているはず

㉛Google Home(Nest Hub)を確認し、次に再生する動画が入っていることを確認する
Nest Hub miniなど画面がない端末は見れないと思うが、動きは同じになるはず
32.jpg

ちなみにnext 改行 URL(複数行入力可)で、割り込みして次に入れることも可能
remove 改行 URL(複数行入力可)で、キューから削除もできる
clearでリストを一括削除できる
連続してaddやnextを送ることも可能
※removeやclearで全部の動画がキューから削除されると、
 一定時間後にYouTubeが立ち上がってしまう模様
 その場合はアプリなどから強制的に終了させる必要がある
※このあたりはcattやcasttubeの動作仕様を見れば色々やれるはず
 また、addなどが打ちにくい場合、自分の好きなように変えてもらえば良い


ここからは蛇足
ラズパイ再起動で自動でwebhookサーバを立ち上げる
㉜再起動時に立ち上げるターミナルgnome-terminalをインストールする
sudo apt-get install gnome-terminal
㉝gnome-terminalのデザインを変える
デフォルトだと背景白で気持ち悪いので、デフォのターミナルと同じ色にする
以下をターミナルから入力
gnome-terminal
編集>Preferencesを選択
33.PNG
㉞色変更
"システムのテーマ色を使用する"のチェックを外し、組み込みのスキームを
黒字に灰色文字にし、パレット>組み込みのスキームをLinux コンソールに変更
34.PNG

㉟フォントサイズ変更
文字>Custom fontのチェックを入れ、フォントサイズを10にする
35.PNG

これでデフォのターミナルっぽい色になる
36.PNG

㊱再起動時に走らせるスクリプト作成
適当なところに以下のようなshファイルを作成
#!/bin/sh
my_server_path=上記スクリプトのフルパス.py
gnome-terminal -e "bash -c \"sudo kill $(pgrep -f $my_server_path);python3 $my_server_path; exec bash\""
実行権限をつけておく
chmod 777 restart_my_serv.sh
㊲隠しフォルダを表示する
起動時に走らせる仕組みとして、今回はautostartを用いる
これは隠しフォルダ以下に配置する必要があるので、まず隠しフォルダを表示させる

エクスプローラを開き、表示>隠しファイルを表示する を選択する
37.PNG

㊳ホームディレクトリ/.config/autostartに移動する
ない場合は新規ディレクトリ作成する
適当な名前.desktop というファイルを作成する

38.PNG

㊴autostartの内容を記述する
上記.desktopを開き、以下の内容を記載する
[Desktop Entry]
Exec=再起動用スクリプトのフルパス.sh
Type=Application
Name=my_serv
Terminal=false
これで、ラズパイを再起動すれば自動でwebhookサーバーが立ち上がっているはず

ただし、この手順ではSSL更新後の証明書再読み込みのための定期再起動が出来ないので、
何ヶ月も再起動させず、立ち上がりっぱなしにしておくと認証エラーになると思われる。
crontabなどで再起動出来ると思われるが、色々試したが出来なかったので
いつか出来るようになったら追記する


2020/12/05 0:40追記
最初のYouTube再生のcatt要求で、「YouTube said: Unable to extract video data」
といったようなエラーが出る場合がある。
cattの仕組みとして、youtube-dlを用いるようにしているようで、
YouTubeの仕様が変更された場合、こちらのモジュールが対応していないと
このようなエラーが出る模様。
youtube-dl側に対応してもらう必要があるが、最新版で解消したので記載しておく。

以下コマンドを実行し、webhookサーバを再起動
pip3 install --upgrade youtube-dl


参考にした記事
CATTを使ってラズパイからChromecastやNest Hubに動画やWebサイトをキャストしてみる - Qiita
【Ubuntu16.04.5】PythonのFlaskをHTTPS化 - Qiita
casttube · PyPI
【Python】YouTubeの動画URLから動画IDを取り出す【メモ書き】 - Qiita
【python】os.systemはもう古い!?subprocessでコマンドを実行しよう! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

nice!(0)  コメント(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。