2023年1月30日月曜日

Raspberry Piに2つのBluetoothスピーカーを接続してPythonで再生する

Raspberry Piに2つのBluetoothスピーカーを接続して、Pythonでその2つのスピーカーのうち1台を指定して音声ファイルを再生してみる。


環境

Raspberry Pi B+とRaspberry Pi OS。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ python3 -V
Python 3.9.2


2つのBluetoothスピーカーをRaspberry Piに接続する

今回使用するのはRaspberry Pi B+で、本体にBluetoothの機能がないので、USB接続のBluetoothアダプタ(ドングル)を2つ用意して接続する。Bluetoothアダプタが認識されているか確認しておく。

$ lsusb
Bus 001 Device 004: ID xxxx:xxxx Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID xxxx:xxxx Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
...


必要なモジュールのインストールと設定

Raspberry PiにBluetoothスピーカーをつなげると同様に、サウンドサーバとそのbluetoothモジュールをインストールし、使用するユーザ(pi)をbluetoothグループに追加しておく。

以下コマンドでモジュールのインストール。

$ sudo apt install pulseaudio pulseaudio-module-bluetooth

ユーザーをbluetoothグループに追加。

$ sudo usermod -a -G bluetooth pi
$ sudo reboot


2つのBluetoothスピーカーを接続

2つのBluetoothアダプタそれぞれにBluetoothスピーカーを接続する。基本的な接続方法はRaspberry PiにBluetoothスピーカーをつなげると同じだが、Bluetoothアダプタが2つ以上ある場合は、接続設定する前に、Bluetoothアダプタを選択しておく。bluetoothctlのlistコマンドでBluetoothアダプタの一覧が表示される(defaultが現在選択されているアダプタ)ので、selectコマンドで設定するアダプタを選択する。

$ bluetoothctl
[bluetooth]# list
Controller 11:11:11:11:11:11 raspberrypi #2 [default]
Controller 22:22:22:22:22:22 raspberrypi

以下のようにアドレスを指定して選択。

[bluetooth]# select 22:22:22:22:22:22
Controller 22:22:22:22:22:22 raspberrypi [default]

アダプタを選択したら、スピーカーを接続する。devicesコマンドで接続可能なBluetoothデバイスの一覧が表示されるので、接続するBluetoothスピーカーが一覧にあるのを確認する。

[bluetooth]# power on
[bluetooth]# scan on
[bluetooth]# devices

アドレスを指定してBluetoothスピーカーに接続する。

[bluetooth]# pair XX:XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX:XX
[bluetooth]# quit

selectコマンドでアダプタを切り替えて、もうひとつのBluetoothスピーカーも同様に接続する。


PythonでBluetoothスピーカーから音声ファイルを再生する

Pythonで音声ファイルを再生するには音声ファイルを再生するPythonコードをcron実行するのように、VLCで音声ファイルを再生する。まずは、PythonでVLCを制御するモジュールをインストール。

$ pip3 install python-vlc
$ pip3 show python-vlc
Name: python-vlc
Version: 3.0.16120
...

今回は2つのBluetoothスピーカーのうちひとつを選択して、音声ファイルを再生する。やり方としては、python-vlcでスピーカー名とそのアドレスを取得できるので、それをもとに再生するスピーカーを選択する。スピーカー名とそのアドレスは以下のように音声出力デバイス情報から取得できる。

import vlc

player = vlc.MediaPlayer()
mod = player.audio_output_device_enum()
while mod:
    mod = mod.contents
    address = mod.device.decode('utf-8').split('.')[1]
    print('--------------------')
    print('device =', mod.device)
    print('address =', address)
    print('name =', mod.description.decode('utf-8'))
    mod = mod.next

上記コードを実行すると以下のような結果が得られる。ひとつ目の内部オーディオはRaspberry Pi本体のオーディオ出力。今回は同じモデルのBluetoothスピーカーを2台使用しているので、スピーカー名は同じになる。このスピーカー名とアドレスで音声ファイルを再生するスピーカーを選択する。

device = b'alsa_output.platform-bcm2835_audio.analog-stereo'
address = platform-bcm2835_audio
name = 内部オーディオ Analog Stereo
--------------------
device = b'bluez_sink.AA_AA_AA_AA_AA_AA.a2dp_sink'
address = AA_AA_AA_AA_AA_AA
name = Speaker
--------------------
device = b'bluez_sink.BB_BB_BB_BB_BB_BB.a2dp_sink'
address = BB_BB_BB_BB_BB_BB
name = Speaker

以下のPythonスクリプトで、接続された2つのBluetoothスピーカーのうち1台を指定して音声ファイルを再生する。このスクリプトと同じディレクトリに再生する音声ファイル(ここではtest.mp3)を配置しておく。

import os
import sys
from time import sleep

import vlc

# スピーカー名とアドレスの設定
SPEAKER1 = {'name': 'Speaker', 'address': 'XX_XX_XX_XX_XX_XX'}
SPEAKER2 = {'name': 'Speaker', 'address': 'YY_YY_YY_YY_YY_YY'}
SPEAKERS = [SPEAKER1, SPEAKER2]

# 再生する音声ファイル
# スクリプトと同じディレクトリに配置する
SOUND_FILE = 'test.mp3'


def get_output_device(player, speaker):
    """指定のスピーカーを取得する"""
    speaker_name = speaker['name']
    speaker_address = speaker['address']
    mod = player.audio_output_device_enum()
    while mod:
        mod = mod.contents
        address = mod.device.decode('utf-8').split('.')[1]
        if speaker_name == mod.description.decode('utf-8') and speaker_address == address:
            return mod.device
        mod = mod.next

    return None


def play(speaker, volume):
    """指定したスピーカーで音声ファイルを再生"""
    script_dir = os.path.dirname(os.path.realpath(__file__))
    media_path = os.path.join(script_dir, SOUND_FILE)
    player = vlc.MediaPlayer(media_path)
    device = get_output_device(player, speaker)
    if device:
        player.audio_output_device_set(None, device)
        player.audio_set_volume(volume) # ボリューム設定
        player.play()

        # 再生が終わるまで実行を継続する
        sleep(10)


def main():
    # 引数としてスピーカーID(0か1)を指定する(デフォルトは0)
    speaker_id = 0
    if len(sys.argv) >= 2 and sys.argv[1] in ('0', '1'):
        speaker_id = int(sys.argv[1])

    play(SPEAKERS[speaker_id], volume=100)

if __name__ == '__main__':
    main()

2つ目のスピーカーから再生する場合は次のように実行する。

$ python3 sample.py 1


2023年1月16日月曜日

InstagramグラフAPIとPythonで指定ハッシュタグ付きの最新投稿を取得する

InstagramグラフAPIを使うとInstagramの投稿情報などを取得できる。ただ、APIの使用に必要なアクセストークンの取得にやや手間がかかる。Pythonで指定したハッシュタグ付きの最新投稿を取得してみたので、アクセストークンの取得も含めて手順をまとめておく。


環境

WSL2(Ubuntu20.04)。

$ lsb_release -dr
Description:    Ubuntu 20.04.5 LTS
Release:        20.04
$ python3 -V
Python 3.8.10


APIアクセストークンの取得

InstagramグラフAPIを使うため、FacebookグラフAPIのアクセストークンを取得する。FacebookとInstagramのアカウントはあるとする。Instagramアカウントはプロアカウントに切り替えておく。


FacebookページとInstagramプロアカウントの紐づけ

アクセストークン取得前に、FacebookページとInstagramプロアカウントを紐づけておく。Facebookページがなければ、Facebookのメニューの[ページ]を開いて、[新しいページを作成]から作成できる。

InstagramプロアカウントのFacebookページへの紐づけは、Facebookページの画面の[Instagram投稿を宣伝]>[アカウントをリンク]で行う。


Facebook for Developersでアプリの作成

次に、Facebook for Developersアカウントを作成する。Meta  for Developersにアクセスして、アカウントを作成する。アカウントを作成したら、マイアプリの画面でアプリを作成する。今回は以下の設定で作成。

アプリタイプ:ビジネス
アプリ名:テストアプリ(任意名)
アプリの連絡先メールアドレス:登録メールアドレス(デフォルト)
ビジネスマネージャーアカウントをお持ちですか?:ビジネスマネージャアカウントが設定されていません(デフォルト)

アプリを作成したら、[アプリに製品を追加]からInstagramグラフAPIの[設定]ボタンを押す。これで、メニューの商品欄にInstagramグラフAPIが追加される。ここまでやったら、画面右下の[変更を保存]ボタンを押す。


permissonの追加

Facebook for Developersのトップメニューの[ツール]>[グラフAPIエクスプローラー]を開く。[Metaアプリ]で作成したアプリを選択し、[ユーザーまたはページ]で[ページアクセストークン]からFacebookページを選択してから、permissionを追加する。▼をクリックし、さらにOtherを開くとInstagramのpermissionが表示される。今回は以下5つを追加。

  • instagram_basic
  • instagram_content_publish
  • instagram_manage_comments
  • instagram_manage_insights
  • instagram_manage_messages



permissionを追加し、[Generate Access Token]をクリックすると、Facebookでログインのポップアップ画面が表示される。この画面ではFacebookユーザーとして続行をクリック。

次に表示される画面では、今回は[現在のページのみにオプトイン]を選択。Facebookページを選択してから[続行]ボタンを押す。

続く画面では、今回は[現在のInstagramアカウントのみにオプトイン]を選択。Instagramプロアカウントを選択してから[続行]ボタンを押す。

アプリが要求するアクセス許可の確認画面が表示されるので、[続行]で先に進む。次の画面で[OK]を押すと、アクセストークンが表示される。


無期限アクセストークンの取得

ここまでで取得できたアクセストークンには期限があるので、無期限のアクセストークンを取得する。[ツール]>[グラフAPIエクスプローラー]でグラフAPIエクスプローラを開いてて、[Metaアプリ]で対称のアプリを選択し、アクセストークン左のiアイコンをクリックするとアクセストークン情報が表示される。


画面右下の[アクセストークンツールで開く]をクリックして、アクセストークンツールを開き、[アクセストークンを延長]をクリックすると、期限のないアクセストークンが表示される。


[デバッグ]ボタンを押すと無期限のアクセストークン情報が表示される。有効期限が「受け取らない」になっていればOK。



InstagramビジネスアカウントID の取得

アクセストークンのほかにAPI使用に必要なInstagramビジネスアカウントIDを取得する。Facebook for Developersのトップメニューの[ツール]>[グラフAPIエクスプローラー]を開く。クエリに「me?fields=accounts{instagram_business_account}」を指定して、[送信]ボタンを押して表示されるレスポンスのinstagram_business_accountがInstagramビジネスアカウントID。



指定ハッシュタグ付きの最新投稿を取得

無期限アクセストークンとInstagram ビジネスアカウントIDが取得できたので、指定したハッシュタグが付いた最新の投稿をAPIで取得してみる。

はじめに、APIのエンドポイントアクセスに使うRequestsをインストールしておく。

$ pip3 install requests

指定ハッシュタグ付きの最新投稿を取得する手順としては、IG Hashtag SearchでハッシュタグIDを取得して、そのIDをもとにIGハッシュタグ付きの最近のメディアで最新投稿を検索する。以下コードでは、ハッシュタグ「スイーツ」が付いた最新の投稿を取得する。

from pprint import pprint

import requests

API_VER = 'v15.0'
INSTA_ACCOUNT_ID = 'xxxxxxxxxx' # Instagram ビジネスアカウントID
INSTA_ACCESS_TOKEN = 'xxxxxxxxxxx' # 無期限アクセストークン


# ハッシュタグ「スイーツ」のIDを取得
query = 'スイーツ'
ig_hashtag_search_url = f'https://graph.facebook.com/{API_VER}/ig_hashtag_search?user_id={INSTA_ACCOUNT_ID}&q={query}&access_token={INSTA_ACCESS_TOKEN}'
response = requests.get(ig_hashtag_search_url)
res = response.json()['data']
hashtag_id = res[0]['id']
print('hashtag_id=', hashtag_id)

# ハッシュタグ「スイーツ」がついた最新投稿を取得
recent_media_url = f'https://graph.facebook.com/{hashtag_id}/recent_media?user_id={INSTA_ACCOUNT_ID}&fields=id,media_type,media_url,comments_count,like_count,caption,permalink,timestamp&access_token={INSTA_ACCESS_TOKEN}'
response = requests.get(recent_media_url)
res = response.json()['data']
pprint(res)

上記コードを実行すると、以下のような結果が得られる。

hashtag_id= 17843787853011507
[{'caption': 'xxxxx\n'
             '#xxxxx\n'
             '#xxxxx',
  'comments_count': 0,
  'id': '11111111111111',
  'like_count': 0,
  'media_type': 'IMAGE',
  'media_url': ''https://xxxxx',
  'permalink': 'https://www.instagram.com/p/xxxxx/',
  'timestamp': '2023-01-14T07:30:42+0000'},
 {'caption': 'xxxxxxxxxxxxx\n'
             '\n'
             '#xxxxxxxxxxxxxxxxxx',
  'comments_count': 0,
  ...