駆け出しエンジニアの作業ノート

駆け出しエンジニアが作業ノート風にまとめるページ(関係無い事もしばしば)

SpotifyとGoogleの両方でプレイリストを自動生成する

間隔が空いてしまいましたが、プレイリスト自動生成ツールについては相変わらず開発を続けています。

 

psyduck-take-it-easy.hatenablog.com

psyduck-take-it-easy.hatenablog.com

psyduck-take-it-easy.hatenablog.com

 

今回の更新で大きなトピックとしては、Google Musicにも対応した事です。Google ではGCPで各種APIを提供していますが、Google Musicに関しては特に提供されていません。今回は、非公式ながら提供されているAPIを用いました。

 

gmusicapi: an unofficial API for Google Play Music — gmusicapi 11.0.1 documentation

 

自分の感じとして、GoogleにはあるけどSpotifyには無いという楽曲にはいくつか出会っていますが、その逆が無かったので、今回はSpotifyでプレイリスト生成を行い、そこから漏れた楽曲でプレイリスト生成を行うという形で実装を行いました。

 

それ以外のトピックとしては、過去24時間以内に聴いた楽曲をプレイリストから外す実装を行いました。ただ、Last.fmの楽曲データと各媒体で提供されている楽曲表示が微妙に異なる事例があり、その場合にはLast.fm側の取得時に調整する方法で一つ一つ対応しています。

 

また、SpotifyAPIでアーティスト名が英語表記で返ってくる問題に関しては、Watsonの翻訳APIを通すことによって解消しました。

 

では、実際のコードです。

 

# main.py
import
spotipy
import re
import connect_lastfm_api
import create_playlist_to_Spotify_etc
from spotify_token import Spotify_token
from gmusicapi import Mobileclient

api_key = {YOUR_LASTFM_API_KEY}
username = {YOUR_SPOTIFY_USER_NAME}

def input_artist():
artist = input("検索したいアーティスト名を入力して下さい")
page = 1
search_song_list(artist, page)

def search_song_list(artist, page):
list = connect_lastfm_api.get_search_song_list(artist, api_key, page)
for i in range(len(list)):
song_list = list[i]
song = song_list["name"]
print(song)
yes_or_no = input("この中に探したい曲はありましたか?(y/n)")
if yes_or_no == "y":
search_track(artist)
elif yes_or_no == "n":
page = page + 1
search_song_list(artist, page)
else:
print("正しい入力ではありません")

def search_track(artist):
song = input("曲名を入力して下さい")
create_playlist(artist, song)

def create_playlist(artist, song):
playlist = []
first_input = (artist, song)
playlist.append(first_input)
count = 0
recent_track_list = connect_lastfm_api.recent_play_song(api_key)
genarate_playlist(artist, song, playlist, first_input, count, recent_track_list)

def genarate_playlist(artist, song, playlist, first_input, count, recent_track_list):
similar_track_list = connect_lastfm_api.get_similar_track(artist, song, api_key)
print(similar_track_list)
print(recent_track_list)
playlist_limit = 15
if len(similar_track_list) == 0:
track = (artist, song)
if track in playlist:
pass
else:
playlist.append(track)
if len(playlist) >= playlist_limit:
print(playlist)
get_empty_Spotify_playlist(first_input, playlist)
else:
page = 1
try:
list = connect_lastfm_api.get_search_song_list(artist, api_key, page)
song_list = list[count]
song = song_list["name"]
count = count + 1
genarate_playlist(artist, song, playlist, first_input, count, recent_track_list)
except KeyError:
artist = connect_lastfm_api.similar_artist_search(artist, api_key)
count = 0
genarate_playlist(artist, song, playlist, first_input, count, recent_track_list)
except IndexError:
artist = connect_lastfm_api.similar_artist_search(artist, api_key)
count = 0
genarate_playlist(artist, song, playlist, first_input, count, recent_track_list)
else:
for i in range(len(similar_track_list)):
track = similar_track_list[i]
if len(playlist) >= playlist_limit:
break
elif track in playlist:
continue
elif track in recent_track_list:
continue
else:
playlist.append(track)
if len(playlist) < playlist_limit:
search_data = playlist[len(playlist)-1]
artist = search_data[0]
song = search_data[1]
count = count + 1
genarate_playlist(artist, song, playlist, first_input, count, recent_track_list)
else:
print(playlist)
get_empty_Spotify_playlist(first_input, playlist)

def get_empty_Spotify_playlist(first_input, playlist):
playlist_name = first_input[0] + "の" + first_input[1] + "から始まるおすすめプレイリスト"
ST = Spotify_token(username)
token = ST.set()
sp = spotipy.Spotify(auth=token)
sp.trace = False
playlistts = sp.user_playlist_create(username, playlist_name)
playlist_id = playlistts["id"]
song_ids = []
not_append_list = []
for i in range(len(playlist)):
search_str = playlist[i]
s_artist = search_str[0]
s_song = search_str[1]
search_str = s_artist + " " + s_song
result = sp.search(search_str, limit=1)
tracks = result["tracks"]
items = tracks["items"]
similar_artist_list = connect_lastfm_api.get_similar_artist_list(s_artist, api_key)
if items == []:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
continue
api_artist = items[0]["album"]["artists"][0]["name"]
print(api_artist)
api_song = items[0]["name"]
api_song = re.sub("\t", "", api_song)
song_id = items[0]["id"]
print(api_song)
search_data = (s_artist, s_song)
api_data = (api_artist, api_song)
result = create_playlist_to_Spotify_etc.create_playlist(search_data, api_data, similar_artist_list)
if result == "add":
song_ids.append(song_id)
else:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
Spotify_playlist_input(playlist_id, song_ids, not_append_list)

def Spotify_playlist_input(playlist_id, song_ids, not_append_list):
ST = Spotify_token(username)
token = ST.set()
if token:
sp = spotipy.Spotify(auth=token)
sp.trace = False
results = sp.user_playlist_add_tracks(username, playlist_id, song_ids)
print(results)
if len(not_append_list) != 0:
print("以下のリストの曲はSpotifyで配信されていないので、Google Play Musicで作成します")
print(not_append_list)
get_empty_google_music_playlist(not_append_list)
else:
print("Can't get token for", username)

def get_empty_google_music_playlist(not_append_list):
gmail = {YOUR_GOOGLE_ACCOUNT}
password = {YOUR_GOOGLE_PASSWORD}
android_id = Mobileclient.FROM_MAC_ADDRESS
name = "Spotifyから漏れた楽曲リスト"
api = Mobileclient()
logged_in = api.login(email=gmail, password=password, android_id=android_id, locale="ja")
playlist_id = api.create_playlist(name=name, description=None, public=False)
search_google(not_append_list, playlist_id, api)

def search_google(playlist, playlist_id, api):
song_ids = []
not_append_list = []
for i in range(len(playlist)):
track_data = playlist[i]
s_artist = track_data[0]
s_song = track_data[1]
search_str = s_artist + " " + s_song
search_data = api.search(query=search_str, max_results=None)
song_result = search_data["song_hits"]
try:
api_artist = song_result[0]["track"]["artist"]
api_song = song_result[0]["track"]["title"]
song_id = song_result[0]["track"]["storeId"]
print(api_artist)
print(api_song)
similar_artist_list = connect_lastfm_api.get_similar_artist_list(s_artist, api_key)
search_data = (s_artist, s_song)
api_data = (api_artist, api_song)
result = create_playlist_to_Spotify_etc.create_playlist(search_data, api_data, similar_artist_list)
if result == "add":
song_ids.append(song_id)
else:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
except IndexError:
print(song_result)
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
google_playlist_input(playlist_id, song_ids, not_append_list, api)

def google_playlist_input(playlist_id, song_ids, not_append_list, api):
results = api.add_songs_to_playlist(playlist_id=playlist_id, song_ids=song_ids)
print(results)
if len(not_append_list) != 0:
print("以下のリストの曲はSpotify及びGoogle Play Musicで配信されていません")
print(not_append_list)


if __name__ == '__main__':
input_artist()

  

 

# connect_lastfm_api.py
import
urllib.request
import urllib.parse
import json
import time

def get_search_song_list(artist, api_key, page):
artist = urllib.parse.quote(artist)
artist_api1 = "http://ws.audioscrobbler.com/2.0/?method=artist.gettoptracks&artist="
artist_api2 = "&autocorrect=1&page="
page = str(page)
artist_api3 = "&api_key="
artist_api4 = "&format=json"
call_api = artist_api1 + artist + artist_api2 + page + artist_api3 + api_key + artist_api4
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
track = data["toptracks"]
list = track["track"]
return list

def get_similar_track(artist, song, api_key):
artist = urllib.parse.quote(artist)
song = urllib.parse.quote(song)
track_api1 = "http://ws.audioscrobbler.com/2.0/?method=track.getsimilar&artist="
track_api2 = "&track="
track_api3 = "&autocorrect=1&api_key="
track_api4 = "&format=json"
call_api = track_api1 + artist + track_api2 + song + track_api3 + api_key + track_api4
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
similartracks = data["similartracks"]
track = similartracks["track"]
similar_track_list = []
for i in range(len(track)):
list = track[i]
match = list["match"]
if match < 0.1:
continue
else:
song = list["name"]
similar_song_artist = list["artist"]["name"]
similar_track = (similar_song_artist, song)
similar_track_list.append(similar_track)
return similar_track_list

def similar_artist_search(artist, api_key):
artist = urllib.parse.quote(artist)
artist_api1 = "http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist="
artist_api2 = "&autocorrect=1&api_key="
artist_api3 = "&format=json"
call_api = artist_api1 + artist + artist_api2 + api_key + artist_api3
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
similarartists = data["similarartists"]
artist_list = similarartists["artist"]
list = artist_list[0]
most_similar_artist = list["name"]
return most_similar_artist

def recent_play_song(api_key):
now = time.time()
recent = now - 86400
recent = str(recent)
recent_api1 = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user="
user_name = {YOUR_LASTFM_USER_NAME}
recent_api2 = "&from="
recent_api3 = "&api_key="
recent_api4 = "&format=json"
call_api = recent_api1 + user_name + recent_api2 + recent + recent_api3 + api_key + recent_api4
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
recenttracks = data["recenttracks"]
track = recenttracks["track"]
recent_track_list = []
for i in range(len(track)):
list = track[i]
artist = list["artist"]["#text"]
song = list["name"]
recent_track = (artist, song)
if recent_track in recent_track_list:
continue
else:
recent_track_list.append(recent_track)
return recent_track_list

def get_loved_track(api_key):
loved_api1 = "http://ws.audioscrobbler.com/2.0/?method=user.getlovedtracks&user="
user_name = {YOUR_LASTFM_USER_NAME}
loved_api2 = "&api_key="
loved_api3 = "&format=json"
call_api = loved_api1 + user_name + loved_api2 + api_key + loved_api3
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
lovedtracks = data["lovedtracks"]
attr = lovedtracks["@attr"]
limit = attr["total"]
limit_api = "&limit="
call_api = loved_api1 + user_name + limit_api + limit + loved_api2 + api_key + loved_api3
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
lovedtracks = data["lovedtracks"]
track = lovedtracks["track"]
loved_list = []
for i in range(len(track)):
list = track[i]
try:
song = list["name"]
artist = list["artist"]["name"]
loved_data = (song, artist)
loved_list.append(loved_data)
except KeyError:
continue
return loved_list

def get_similar_artist_list(artist,api_key):
artist = urllib.parse.quote(artist)
artist_api1 = "http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist="
artist_api2 = "&autocorrect=1&api_key="
artist_api3 = "&format=json"
call_api = artist_api1 + artist + artist_api2 + api_key + artist_api3
print(call_api)
address_json = urllib.request.urlopen(call_api)
data = json.loads(address_json.read())
try:
similarartists = data["similarartists"]
artist_list = similarartists["artist"]
similar_artist_list = []
for i in range(len(artist_list)):
list = artist_list[i]
similar_artist = list["name"]
match = list["match"]
match = float(match)
if match < 0.1:
continue
else:
similar_artist_list.append(similar_artist)
return similar_artist_list
except KeyError:
similar_artist_list = []
return similar_artist_list

 

# create_playlist_to_Spotify_etc.py
import
lev_distance
import connect_watson_translate


def create_playlist(search_data, api_data, similar_artist_list):
s_artist = search_data[0]
s_song = search_data[1]
api_artist = api_data[0]
api_song = api_data[1]
if api_artist == s_artist:
print("アーティスト名完全一致")
print("OK")
result = "add"
# 類似アーティストの中に含まれていた場合
elif api_artist in similar_artist_list:
# 曲名完全一致の場合は拾う
if api_song == s_song:
print("曲名完全一致")
print("OK")
result = "add"
else:
song_word_distance = lev_distance.calc_distance(api_song, s_song)
if song_word_distance < 6:
print("曲名類似")
print("OK")
result = "add"
else:
result = "not"
else:
api_artist = connect_watson_translate.translate_word(api_artist)
artist_word_distance = lev_distance.calc_distance(api_artist, s_artist)
song_word_distance = lev_distance.calc_distance(api_song, s_song)
if artist_word_distance < 9 and song_word_distance < 9:
print("アーティスト名類似")
print("OK")
result = "add"
elif api_artist in similar_artist_list:
print("アーティスト名類似")
print("OK")
result = "add"
else:
result = "not"
return result

 

# spotify_token.py
import
spotipy.util as util

class Spotify_token:
def __init__(self, username):
self.username = username
self.scope = 'playlist-modify-public'

def set(self):
token = util.prompt_for_user_token(self.username, self.scope)
return token

  

 

# lev_distance.py
def
calc_distance(a, b):
if a == b: return 0
a_len = len(a)
b_len = len(b)
if a == "":
return b_len
if b == "":
return a_len
matrix = [[] for i in range(a_len + 1)]
for i in range(a_len + 1):
matrix[i] = [0 for j in range(b_len+1)]
for i in range(a_len + 1):
matrix[i][0] = i
for j in range(b_len + 1):
matrix[0][j] = j
for i in range(1, a_len + 1):
ac = a[i-1]
for j in range(1, b_len + 1):
bc = b[j-1]
cost = 0 if (ac == bc) else 1
matrix[i][j] = min([matrix[i-1][j]+1, matrix[i][j-1]+1,matrix[i-1][j-1] + cost])
return matrix[a_len][b_len]

 

# connect_watson_translate.py
from
watson_developer_cloud import LanguageTranslatorV2 as LanguageTranslator

def translate_word(text):
langage_translator = LanguageTranslator(username=
{YOUR_USER_NAME}, password= {YOUR_PASSWORD})
text_list = []
text_list.append(text)
translation = langage_translator.translate(
text=text_list, model_id="en-ja")
translations = translation[
"translations"]
word_list = translations[
0]
trans_word = word_list[
"translation"]
return trans_word

 

レーベンシュタイン距離計算のコードはこの本を参考にしました。

 

 

 Spotifyトークン取得は以下を参考にしています。

 

sakanaaas.hateblo.jp

 

では、前回と同じように乃木坂46シンクロニシティを入れてみます。

まず、生成された大元のプレイリストがこちらです。

 

[('乃木坂46', 'シンクロニシティ'), ('乃木坂46', '君の名は希望'), ('乃木坂46', '命は美しい'), ('E-Girls', 'ごめんなさいのKissing You'), ('E-Girls', 'Mr.Snowman'), ('HKT48', '桜、みんなで食べた'), ('HKT48', '12秒'), ('AKB48', 'Green Flash'), ('高橋みなみ', 'Jane Doe'), ('AKB48', '希望的リフレイン'), ('Not yet', '西瓜BABY'), ('前田敦子', 'タイムマシンなんていらない'), ('SKE48', 'オキドキ'), ('指原莉乃', '初恋ヒルズ'), ('渡辺美優紀', 'わるきー')]

 

Spotifyのプレイリストです。

 

f:id:Psyduck_take_it_easy:20180506181335p:plain

 

結果、以下の曲がSpotifyプレイリストから漏れてしまいました。

 

[('HKT48', '桜、みんなで食べた'), ('HKT48', '12秒'), ('AKB48', 'Green Flash'), ('Not yet', '西瓜BABY'), ('渡辺美優紀', 'わるきー')] 

 

漏れた楽曲よりGoogleのプレイリスト生成を行います。

 

f:id:Psyduck_take_it_easy:20180506181426p:plain

 

結果、最後に以下の曲が残ってしまいました。

 

[('AKB48', 'Green Flash'), ('渡辺美優紀', 'わるきー')] 

 

このうち、「わるきー」に関しては配信されているものの、楽曲表記が異なるため上手く拾えませんでした。「Green Flash」に関しては配信されていないようです。詳細は不明です。

 

今後についてはサーバー等で本来の再生順に即して、外部から再生制御が行えないか研究してみたいと思います。