#はじめに
以前作った「iTunesで再生中の曲の歌詞をPythonで自動表示させよう」に大幅に機能を追加しました。
#改良箇所
- 曲のスキップに対応
- 表示の高速化
- アプリの制御
- 自動スクロール
- 歌詞検索精度の向上
#曲のスキップに対応
一度曲の歌詞ページを開いたらその曲が終わるまでそのページを表示し続ける仕様だったのを、次の曲にスキップしたら歌詞も次の曲を表示させるようにしました。
while True:
start = time.time() # delete
# ~~~~~~~~
time.sleep(music_time - (time.time() - start)) # delete
prev_track_name = "" # add
while True:
# 再生中の曲名、アルバム名、アーティスト名、曲の長さ
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
# add-start
if name == prev_track_name:
time.sleep(1)
continue
prev_track_name = name
# add-end
# ~~~~~~~~
time.sleep(1) # add
#表示の高速化
歌詞ページの表示が完了するまでに時間がかかっていたため、タイムアウトを設定しました。
driver = webdriver.Chrome(executable_path="chromedriver")
driver.get("https://www.google.com/") # 初期タブ
driver.set_page_load_timeout(5) # 読み込みの最大待ち時間(この場合 5 秒間) # add
歌詞自体は 5 秒もあれば読み込みができますが、それ以外の画像等に時間がかかってページ読み込み時間が長くなってしまうため、5 秒を過ぎたら強制的に終了させます。ついでに「歌詞の位置までスクロール」も、歌詞の位置を探してその位置までスクロールするように変更しました。
while True:
# ~~~~~~~~
try:
# ~~~~~~~~
# 新しいタブで開く
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])
while True:
# ~~~~~~~~
try:
# ~~~~~~~~
# 新しいタブで開く
driver.execute_script("window.open()")
driver.switch_to.window(driver.window_handles[1])
# add-start
try:
driver.get("https://www.uta-net.com/" + name_url)
except:
pass
# 歌詞の位置までスクロール
driver.execute_script("arguments[0].scrollIntoView();", driver.find_element_by_id("view_kashi"))
# add-end
# 前のタブを閉じる
driver.switch_to.window(driver.window_handles[0])
driver.close()
driver.switch_to.window(driver.window_handles[0])
#アプリの制御
ほぼおまけですが、ターミナル上でも「再生」「次の曲へ」などを制御できるようにしました。
まず基本的な操作です。
music = appscript.app("Music")
# 再生
music.play()
# 一時停止
music.pause()
# 再生・一時停止
music.playpause()
# 前のトラックへ
music.previous_track()
# 次のトラックへ
music.next_track()
これをそのまま使います。
また、通常の input()
だと入力が行われるまで待機して他の処理を行うことができないため、タイムアウト付き入力を利用しました。
from select import select
import sys
# 引数は順に「読み込み」「書き込み」「例外」「待機時間」で、今回は読み込みのみ使用
# 待機時間は 0.5 秒に指定
# 0.5 秒経って入力がなければ空のリストが返される
read, _, _ = select([sys.stdin], [], [], 0.5)
if read:
command = sys.stdin.readline().strip()
else:
command = ""
以上をそのままコードの中に入れていきます。
while True:
# Music アプリにアクセスする
music = appscript.app("Music")
# add-start
# ---- アプリ制御 ----
# タイムアウト付き入力
read, _, _ = select([sys.stdin], [], [], 0.5)
if read:
command = sys.stdin.readline().strip()
else:
command = ""
if command == "b": # 前のトラックへ
music.previous_track()
elif command == "p": # 再生・一時停止
music.playpause()
elif command == "n": # 次のトラックへ
music.next_track()
# add-end
# ~~~~~~~~
#自動スクロール
歌詞全体が表示できないこともあるため、歌詞が自動でスクロールされるようにしました。
while True:
# ~~~~~~~~
if name == prev_track_name:
time.sleep(1)
# add-start
count += 1
if count % 5 == 0:
driver.execute_script(
"window.scrollTo(window.pageXOffset, window.pageYOffset + " + str(kashi_length // music_time * 5) + ");"
)
# add-end
continue
# ~~~~~~~~
# 新しいタブで開く
driver.execute_script("window.open()")
driver.switch_to.window(driver.window_handles[1])
try:
driver.get("https://www.uta-net.com/" + name_url)
except:
pass
# 歌詞の位置までスクロール
driver.execute_script("arguments[0].scrollIntoView();", driver.find_element_by_id("view_kashi"))
# add-start
# ページ上の歌詞部分の長さ
kashi_length = driver.find_element_by_id("view_mylink").location["y"] - \
driver.find_element_by_id("view_kashi").location["y"]
# add-end
# 前のタブを閉じる
driver.switch_to.window(driver.window_handles[0])
driver.close()
driver.switch_to.window(driver.window_handles[0])
ページ内の歌詞部分の長さを取得し、 (歌詞の長さ / 曲の長さ) * 5 下にスクロール を 5 秒おきに実行することで程良いペースでスクロールしています。
#歌詞検索精度の向上
まずは全角アルファベットを半角に変換します。また、検索の際に「完全一致」だったのを「が含まれる」に変更しました。
while True:
# ~~~~~~~~
# ---- 歌詞サイトにアクセス ----
# 全角を半角に # add
name = str(name).translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))
name = parse.quote(name) # パーセントエンコーディング
url = "https://www.uta-net.com/search/?Aselect=2&Keyword=" + name + "&Bselect=3&x=0&y=0" # Bselect=4 を Bselect=3 に
またアクセス数の多いページに付く王冠マークの影響で歌手名の位置がずれることがあるため、修正しました。
while True:
# ~~~~~~~~
try:
# ~~~~~~~~
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 or i == 2: # 歌手名 # 1 だけだったところに 2 を追加
if tag_a.string == artist:
flag = True
break
if flag:
break
#コード全体
import time
import sys
from select import select
import appscript
from urllib import request, parse
from bs4 import BeautifulSoup
from selenium import webdriver
driver = webdriver.Chrome(executable_path="chromedriver")
driver.get("https://www.google.com/") # 初期タブ
driver.set_page_load_timeout(5) # 回線の速さによってはもっと短くても大丈夫
music_time = 0
prev_track_name = ""
count = 0
kashi_length = 0
while True:
# Music アプリにアクセスする
music = appscript.app("Music")
# ---- アプリ制御 ----
# タイムアウト付き入力
read, _, _ = select([sys.stdin], [], [], 0.5)
if read:
command = sys.stdin.readline().strip()
else:
command = ""
if command == "b": # 前のトラックへ
music.previous_track()
elif command == "p": # 再生・一時停止
music.playpause()
elif command == "n": # 次のトラックへ
music.next_track()
# ---- 曲名取得 ----
# 再生中の曲名、アルバム名、アーティスト名、曲の長さ
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
# music_time を秒数に
music_time = music_time.split(":")
music_time = int(music_time[0]) * 60 + int(music_time[1])
if name == prev_track_name:
time.sleep(1)
count += 1
if count % 5 == 0:
driver.execute_script(
"window.scrollTo(window.pageXOffset, window.pageYOffset + " + str(kashi_length // music_time * 5) + ");"
)
continue
prev_track_name = name
print("{} / {} / {}".format(name, artist, album))
# ---- 歌詞サイトにアクセス ----
# 全角を半角に
name = str(name).translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))
name = parse.quote(name) # パーセントエンコーディング
url = "https://www.uta-net.com/search/?Aselect=2&Keyword=" + name + "&Bselect=3&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 or i == 2: # 歌手名
if tag_a.string == artist:
flag = True
break
if flag:
break
# 新しいタブで開く
driver.execute_script("window.open()")
driver.switch_to.window(driver.window_handles[1])
try:
driver.get("https://www.uta-net.com/" + name_url)
except:
pass
# 歌詞の位置までスクロール
driver.execute_script("arguments[0].scrollIntoView();", driver.find_element_by_id("view_kashi"))
kashi_length = driver.find_element_by_id("view_mylink").location["y"] - \
driver.find_element_by_id("view_kashi").location["y"]
# 前のタブを閉じる
driver.switch_to.window(driver.window_handles[0])
driver.close()
driver.switch_to.window(driver.window_handles[0])
except:
pass
print("b:前 | p:再生・一時停止 | n:次")
print()
time.sleep(1)