3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Raspberry Pi でYouTube目覚ましを作る

Last updated at Posted at 2019-04-06

 初投稿です。頑張っていきます。改善点などあればコメントにて指摘いただけると助かります。

 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形式でダウンロード

公式リポジトリのIssueより

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で実装

sync_youtube.sh

#!/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 ローカルに同期したプレイリストをランダムに一曲再生する

aplay_random.sh
#!/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」

 原因はよくわからないのですが、このエラーが出るので対処します。
 複数回実行を試みると実行されるようです。
 実行が成功するまで試行を続けるようにします。

drive_wakeup.sh
#!/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プラグインにデバッグ機能も欲しいですね。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?