初投稿です。頑張っていきます。改善点などあればコメントにて指摘いただけると助かります。
youtube-dlを使ってyoutube上の公開プレイリストをwavファイル群としてローカルに同期し、cronで任意の時間にローカルに保存した曲をランダムに1曲再生します。
ソースだけ見たい人
=>https://github.com/Nshu/wakeup_youtube
0. 環境
- Raspberry Pi 3
- Raspbian 9.8
- 3.5mmステレオミニジャック端子のスピーカー(Logicool Z120BW)
- USB-DAC(ASIN : B00NMXY2MO)
- ※最初は、raspiの内臓のDACから出力しようとしたが、音が出ない(出力不足?)ので急遽USB-DACを使うことに。
1. いろいろ動作確認
まとめた実装をする前に個々の要素をテストしておきます。
1.1. youtube-dlの動作確認
公式ドキュメントを参考に進めていきます。
1.1.1. インストール
sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
sudo chmod a+rx /usr/local/bin/youtube-dl
1.1.2. youtube動画をwav形式でダウンロード
youtube-dl --extract-audio --audio-format wav -o "output.%(ext)s" https://www.youtube.com/watch?v=[動画ID]
※ -oオプションでファイル名を指定する際、%(ext)sを使わず、直接拡張子(ここではwav)を指定してしまうと、ffmpegがスキップされてしまう。
1.1.3 youtubeの公開プレイリストに含まれる全動画のIDを取得
youtube-dl --get-id "https://youtube.com/playlist?list=[プレイリストID]"
標準出力に動画IDが一行ごとに表示されていきます。
1.2. wavファイルの再生
aplayコマンドを使います。
まず、aplay -l
で使うオーディオデバイスのカード番号とデバイス番号を確認します。
私の環境では、以下のようになりました。
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 0: ALSA [bcm2835 ALSA], デバイス 0: bcm2835 ALSA [bcm2835 ALSA]
サブデバイス: 7/7
サブデバイス #0: subdevice #0
サブデバイス #1: subdevice #1
サブデバイス #2: subdevice #2
サブデバイス #3: subdevice #3
サブデバイス #4: subdevice #4
サブデバイス #5: subdevice #5
サブデバイス #6: subdevice #6
カード 0: ALSA [bcm2835 ALSA], デバイス 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
カード 1: Device [USB Audio Device], デバイス 0: USB Audio [USB Audio]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
今回使いたい、デバイスは
カード 1: Device [USB Audio Device], デバイス 0: USB Audio [USB Audio]
なので、カード番号:1、デバイス番号:0となります。
それから、
aplay -D hw:[カード番号],[デバイス番号] [ファイル名].wav
で再生できます。
2. 本実装
2.1. youtube上の公開プレイリストをローカルに同期させる
まず、公開プレイリスト中の動画のIDリスト(online video IDs : oIDs)と
ローカルに保存した動画のIDリスト(saved video IDs : sIDs)を用意し、
oIDsにはあってsIDsにはない動画は保存し、
sIDsにはあってoIDsにはない動画は削除する。
という方式で同期を実装しようと思います
2.1.1. bashで実装
#!/usr/bin/env bash
readonly WORKING_DIR="${HOME}/wakeup_youtube"
cd ${WORKING_DIR}
export PATH=${PATH}:/usr/local/bin
readonly PLAYLIST_URL="https://www.youtube.com/playlist?list=PLl_bkR2Yd2EF0TShPL2tl7mYb98hvt8OV"
readonly CACHE_DIR="local_cache"
# online video IDs
oIDs=$(youtube-dl --get-id ${PLAYLIST_URL}) || exit 0 # if youtube-dl failed, exit.s
#oIDs=$(cat oIDs_cache.txt)
echo ${oIDs} > oIDs_cache.txt
function all_download () {
for oID in ${oIDs}
do
youtube-dl -x --audio-format wav -o "${CACHE_DIR}/%(id)s.%(ext)s" https://www.youtube.com/watch?v=${oID}
done
}
function download_missing_video () {
# saved video IDs
sIDs=$(ls ${CACHE_DIR} | xargs -i basename {} .wav)
# if sIDs is empty
if [[ -z ${sIDs} ]]
then
all_download
return 0
fi
for oID in ${oIDs}
do
is_oID_in_sID="false"
for sID in ${sIDs}
do
echo "oID : ${oID}"
echo "sID : ${sID}"
if [[ ${oID} = ${sID} ]]
then
is_oID_in_sID="true"
break
#echo "oID : ${oID}, sID : ${sID}"
else
:
#echo "oID : ${oID}, sID : ${sID}"
fi
done
if [[ ${is_oID_in_sID} = "false" ]]
then
youtube-dl -x --audio-format wav -o "${CACHE_DIR}/%(id)s.%(ext)s" https://www.youtube.com/watch?v=${oID}
fi
done
}
function del_excess_video () {
# saved video IDs
sIDs=$(ls ${CACHE_DIR} | xargs -i basename {} .wav)
# Delete excess video
for sID in ${sIDs}
do
is_sID_in_oID="false"
for oID in ${oIDs}
do
if [[ ${sID} = ${oID} ]]
then
is_sID_in_oID="true"
break
fi
done
if [[ ${is_sID_in_oID} = "false" ]]
then
rm ${CACHE_DIR}/${sID}.wav
fi
done
}
download_missing_video
del_excess_video
2.2 ローカルに同期したプレイリストをランダムに一曲再生する
#!/usr/bin/env bash
readonly WORKING_DIR="${HOME}/[WORKING_DIR_NAME]"
cd ${WORKING_DIR}
readonly WAV_DIR="[CACHE_DIR_NAME]"
wavs=($(ls ${WAV_DIR}))
random=$RANDOM
wavs_l=${#wavs[@]}
play_index=$(( ${random} % ${wavs_l} ))
aplay -N -D hw:1,0 ${WAV_DIR}/${wavs[${play_index}]}
2.3 「Device or resource busy」
原因はよくわからないのですが、このエラーが出るので対処します。
複数回実行を試みると実行されるようです。
実行が成功するまで試行を続けるようにします。
#!/usr/bin/env bash
readonly W_DIR="${HOME}/wakeup_youtube"
# if W_DIR is empty, exit
if [[ -z "$(ls ${W_DIR})" ]]; then
exit 0
fi
bash ${W_DIR}/aplay_random.sh
while [ $? -gt 0 ]
do
sleep 1
bash ${W_DIR}/aplay_random.sh
done
echo "finish"
2.4 cronで自動化
後はcronで同期スクリプト、再生スクリプトを任意の時間に起動するように設定するだけです。
主な留意点
- スクリプトの冒頭でワーキングディレクトリまで移動する
- 必要なPATHを通す
- 環境変数SHELLにBashを指定する
以下設定例。
SHELL=/bin/bash
0 4 * * * bash [WORKING_DIR_PATH]/sync_youtube.sh
0 5 * * * bash [WORKING_DIR_PATH]/drive_wakeup.sh
3. まとめ、所感
文法簡潔で、高機能なデバッガもあるPythonで書いた方が楽だと思ったのですがBashの勉強にとBashで実装してみました。
条件評価式のなかで、変数の参照の際に$をつけ忘れると変数名がそのまま文字列として評価されるバグ(仕様)は、bashの-x
オプションつけてトレースしないでいたら見つけるの本当に苦労したと思います。
いつもはbashスクリプトを書くときraspiとのsshセッション上のvimで書いていたのですが、
今回はPycharmにBash_Supportを乗っけて、SFTPでraspi上のファイルをリモートから直接編集する形にしてみました。
リッチなIDEは初学者にやさしくて大変嬉しいです。欲をいえばBash_Supportプラグインにデバッグ機能も欲しいですね。