iTunesで再生中の曲の歌詞をPythonで自動表示させよう

#はじめに
iTunes（Catalina からはミュージックに名前が変わりましたが）で曲を再生しながら歌詞を表示させたいことってありますよね。しかし曲が変わるたびに手動で検索して歌詞を表示させるのは面倒です。面倒なことは Python にやらせましょう。

(2020/01/04 追記)
改良しました。
→ 「[iTunesで再生中の曲の歌詞をPythonで自動表示させよう（改良版）](https://qiita.com/saba/items/22b44e1045dbc3f9b0bc)」

#やりたいこと
Mac 標準アプリの「ミュージック」で曲を再生して、曲が切り替わるたびに自動で歌詞サイトの歌詞を表示させる。

#環境
OS: macOS Catalina バージョン 10.15
言語: Python 3.6
使用ライブラリ: appscript, urllib, BeautifulSoup, selenium
各ライブラリのインストールは PyCharm で行いました。それぞれ pip でもインストールできると思います。

#曲名の取得
まずはミュージックアプリから曲情報を取得します。ここでは appscript を使います。詳しいことはよくわかりませんが、AppleScript を Python で操作できるらしいです。[appscript サイトURL](https://pypi.org/project/appscript/)

まずはアプリにアクセスします。今回はミュージックなので Music になります（Catalina 以前を使用してる場合は iTunes になります）。

```python
music = appscript.app("Music")
```
次に再生中の曲情報を取得します。

```python
name = music.current_track.name.get()  # 曲名
artist = music.current_track.artist.get()  # アーティスト名
album = music.current_track.album.get()  # アルバム名
music_time = music.current_track.time.get()  # 曲の長さ（"分:秒"の文字列です）
```

曲の長さが文字列のままだと面倒なので、秒の数値に直しておきます。曲の長さは曲の切り替わりを判定するのに使います。

```python
music_time = music_time.split(":")
music_time = int(music_time[0]) * 60 + int(music_time[1])
```

#歌詞の表示
##歌詞ページを探す
曲情報が取得できたら、今度はそれを使って歌詞サイトにアクセスしましょう。今回歌詞サイトは [Uta-Net](https://www.uta-net.com/) を使用します。また、サイトへのアクセスにはスクレイピング用ライブラリの BeautifulSoup を使います。Beautiful Soup を用いたスクレイピングの詳細は「[PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)」がわかりやすいです。

まず、サイトにある曲名検索機能を使って検索結果の画面に飛びます。日本語の曲名を検索するためにパーセントエンコーディングを行います。

```python
name = parse.quote(name)  # パーセントエンコーディング
url = "https://www.uta-net.com/search/?Aselect=2&Keyword=" + name + "&Bselect=4&x=0&y=0"

html = request.urlopen(url)
soup = BeautifulSoup(html, "html.parser")
```

検索結果に飛ぶと、同名の曲が一覧として出てきます。HTML を見てみると tr タグ内の a タグで曲名と歌手名が一覧になっているため、その中から目当ての歌手を探します。一致するものが見つかれば次の段階に進みます。

```python
tr = soup.find_all("tr")

name_url = ""  # 目当ての曲の URL を入れる
flag = False
for tag_tr in tr:
    a = tag_tr.find_all("a")
    # 曲名、歌手名、作詞、作曲の順番で格納されている
    for i, tag_a in enumerate(a):
        if i == 0:  # 曲名
            name_url = tag_a.get("href")
        elif i == 1:  # 歌手名
            if tag_a.string == artist:
                flag = True
                break
    if flag:
        break
```

##歌詞ページの表示
今回は自動で歌詞ページを開くだけではなく、次の曲に切り替わったら前のページを閉じる必要があります。そのためにブラウザ上の操作を自動化できる selenium を使います。selenium については「[PythonでSelenium](https://qiita.com/yasudaak/items/fee74db16163db3c9af9)」がわかりやすいです。

まず先ほど取得した URL にアクセスしましょう。全体としては以下のループになります。
1. 曲が変わる
↓
2. 新しいタブを開いて歌詞ページにアクセス
↓
3. 前回のタブを閉じる
↓
4. 1 に戻る

まずはループの前にブラウザを開いておきます。

```python
# Chrome を開く
driver = webdriver.Chrome(executable_path="chromedriver")
# ループの起点の初期タブ
driver.get("https://www.google.com/")
```
次にループ内の 2 ~ 3 を行います。その際歌詞ページの上の余白が邪魔なのでページをスクロールしておきます。

```python
# 新しいタブを開いて移動
driver.execute_script("window.open()")
driver.switch_to.window(driver.window_handles[1])
# 移動先で指定 URL にアクセス
driver.get("https://www.uta-net.com/" + name_url)
# 歌詞の位置までスクロール
driver.execute_script("window.scrollTo(0, 380);")
```


# 完成
これで現在再生している曲の歌詞を表示することができました。あとはこれをループするだけです。以下全体のコードです。

```python

import appscript
from urllib import request, parse
from bs4 import BeautifulSoup
from selenium import webdriver
import time


driver = webdriver.Chrome(executable_path="chromedriver")
driver.get("https://www.google.com/")  # 初期タブ

music_time = 0
while True:
    start = time.time()

    # ---- 曲名取得 ----

    # Music アプリにアクセスする
    music = appscript.app("Music")

    # 再生中の曲名、アルバム名、アーティスト名、曲の長さ
    name = music.current_track.name.get()
    artist = music.current_track.artist.get()
    album = music.current_track.album.get()
    music_time = music.current_track.time.get()  # m:s の str

    print(name, artist, album, music_time)

    # music_time を秒数に直す
    music_time = music_time.split(":")
    music_time = int(music_time[0]) * 60 + int(music_time[1])

    # ---- 歌詞サイトにアクセス ----

    name = parse.quote(name)  # パーセントエンコーディング
    url = "https://www.uta-net.com/search/?Aselect=2&Keyword=" + name + "&Bselect=4&x=0&y=0"

    try:
        html = request.urlopen(url)
        soup = BeautifulSoup(html, "html.parser")

        tr = soup.find_all("tr")

        name_url = ""
        flag = False
        for tag_tr in tr:
            a = tag_tr.find_all("a")
            for i, tag_a in enumerate(a):
                if i == 0:  # 曲名
                    name_url = tag_a.get("href")
                elif i == 1:  # 歌手名
                    if tag_a.string == artist:
                        flag = True
                        break
            if flag:
                break

        # 新しいタブで開く
        driver.execute_script("window.open()")
        driver.switch_to.window(driver.window_handles[1])
        driver.get("https://www.uta-net.com/" + name_url)
        driver.execute_script("window.scrollTo(0, 380);")  # 歌詞の位置までスクロール
        # 前のタブを閉じる
        driver.switch_to.window(driver.window_handles[0])
        driver.close()
        driver.switch_to.window(driver.window_handles[0])

    except:
        pass

    print("now browsing")
    print()

    # 曲の時間待つ
    time.sleep(music_time - (time.time() - start))
```

#おわりに
この記事を書いてる間も無事歌詞ページを表示し続けてくれています。が、不十分な点がいくつかありました。

- 曲名に記号や空白が入っていると対応できない
- そもそも歌詞サイトに載っていないと使えない
- ページを開くたびに歌詞を表示してるウィンドウがアクティブになるため、作業が一瞬（2 秒ほど）止まる

上二つについては try-except によって無視しています（たまに表示できなくてもさほど困らないため）。最後に関しては歌詞を表示する以上しょうがないです。