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

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

Graphillionを使って最長片道切符のルートを算出する

だいぶ間隔が空いてしまいましたが皆さんはお元気でしょうか?

 

さて、皆さんが長距離を移動する際にJRを使う方が多いと思います。JRで移動する際にはみどりの窓口指定席券売機で乗車券等を購入します。

 

この乗車券は「JR線」を「一筆書き」で「同じ駅を2度通らない」という条件であればどんな乗車券でも購入する事が出来ます。この条件で最も長いのが「最長片道切符」です。これに関してはいくつか作品が出ています。

 

最長片道切符の旅 (新潮文庫)

最長片道切符の旅 (新潮文庫)

 

 

 さて、肝心のルートですが、これは「デスクトップ鉄」さんによって、新線開業や廃線があった際に発表されています。

 

http://www.desktoptetsu.com/saichohensen.htm

http://d.hatena.ne.jp/desktoptetsu/

 

ただ、デスクトップ鉄さんはエクセルのソルバー(注:Excel2010以降では動作しない)を用いた手法で計算しているので、今回は別の方法を模索しました。

http://www.geocities.jp/longest_route/download1.htm

 

今回はこちらのブログでGraphillionを用いたやり方が紹介されていたので、それを使いました。

 

halpha656.hatenablog.com

 

データセットを用意して、コードは経路順で出力されるように工夫しました。

 

 

from graphillion import GraphSet as gs
import gc

def root_calc():
filename =
f = open(filename, "r", encoding="utf-8")
lines = f.readlines()
f.close()
univ = []
for line in lines:
dat = line.split(",")
dat[2] = float(dat[2])
edge = tuple(dat[0:3])
univ.append(edge)
del dat
del edge
gc.collect()
w = gs.set_universe(univ)
start = ""
end = ""
paths = gs.paths(start, end)
del w
gc.collect()
for path in paths.max_iter():
#print(path)
root_print(path, start, end)
break

def root_print(path, search, end):
another = ""
for i in path:
if search in i:
root_list = list(i)
print(root_list)
path.remove(i)
root_list.remove(search)
another = root_list[0]
break
else:
another = search
continue
if another != "":
root_print(path, another, end)


if __name__ == '__main__':
root_calc()

 

トウトウ,トウシナ,6.8,東海道,東京→品川
トウシナ,トウオマ,2.4,東海道,品川→大井町
トウオマ,ハマカワ,9.0,東海道,大井町→川崎
ハマカワ,ハマツミ,3.5,東海道,川崎→鶴見
ハマツミ,ハマヒナ,5.3,東海道,鶴見→東神奈川
ハマヒナ,ハマハマ,1.8,東海道,東神奈川→横浜
ハマハマ,ハマフナ,17.7,東海道,横浜→大船
ハマフナ,ハマチサ,12.1,東海道,大船→茅ヶ崎
ハマチサ,ハマコツ,19.1,東海道,茅ヶ崎国府津
ハマコツ,ハマオタ,6.2,東海道,国府津→小田原
ハマオタ,シツミシ,36.8,東海道,小田原→三島
シツミシ,シツヌマ,5.5,東海道,三島→沼津
シツヌマ,シツフシ,20.0,東海道,沼津→富士
シツフシ,シツシツ,34.0,東海道,富士→静岡
シツシツ,シツカケ,49.1,東海道,静岡→掛川
シツカケ,シツシハ,53.1,東海道,掛川新所原
シツシハ,ナコトヨ,11.2,東海道,新所原豊橋
ナコトヨ,ナコオカ,32.3,東海道,豊橋→岡崎
ナコオカ,ナコナヤ,36.8,東海道,岡崎→金山
ナコナヤ,ナコナコ,3.3,東海道,金山→名古屋
ナコナコ,ナコヒワ,4.0,東海道,名古屋→枇杷島
ナコヒワ,ナコキフ,26.3,東海道,枇杷島→岐阜
ナコキフ,キトマイ,49.6,東海道,岐阜→米原
キトマイ,キトクサ,45.5,東海道,米原草津
キトクサ,キトヤシ,16.7,東海道,草津→山科
キトヤシ,キトキト,5.5,東海道,山科→京都
キトキト,オサシオ,39.0,東海道,京都→新大阪
オサシオ,オサオサ,3.8,東海道,新大阪→大阪
オサオサ,オサアマ,7.7,東海道,大阪→尼崎
オサアマ,コヘコヘ,25.4,東海道,尼崎→神戸
 

 

データセットは上記の様にしました。JRの駅には各駅ごとに電略記号が存在し必ずカナ2文字で省略されます(一部例外あり)。ただ、地理的に離れている場合ですと同じ略記が使い回されている場合があります。(例:熊谷と熊本の「クマ」等) そこで、各駅の略記の上に駅を管轄する支社の略記を被せて、重複が起こらないようにしました。

 

現在のルートでは、「稚内」と「肥前山口」がそれぞれスタートとゴールになっており(ルートの関係上逆は不可能)スタートとゴールにはそれぞれ対応する略記を入れます。

 

結果、現在デスクトップ鉄さんが発表しているルートとほぼ同じになりました。ルート上「肥前山口」は2度通る(2回目でゴール)ので、その部分を補完する必要があります。今後は、かつて、国鉄及びJRだった路線等を含めたルートでの最長片道切符のルート算出に挑んでみたいと思います。

 

ちなみに、現在のルート図は下のリンクにあります。

 

http://mikaka.org/kohorogi/rail/lop/railmap/mapindex.cgi?route=54

FlaskでWebアプリを作る

お久しぶりです。だいぶ、日が空いてしまいましたが、相変わらず開発を続けています。

 

さて、GUIやクロスプラットプラットでのリリースを考えていましたが、自分自身の実力も踏まえWebアプリでのリリースとしました。

 

Webアプリにするにあたり、CUIより変更した部分があります。

 

1,ログイン機能

 

これは、SpotifyGoogleアカウントを使うので、アカウントの識別用として実装しました。

 

2,「嫌いな曲」登録機能

 

システムが生成したプレイリストの中に人によっては「嫌い」と思うような曲が含まれる場合があります。その場合に備え、嫌いな曲を除外してプレイリストを再生成する機能を実装しました。また、曲はDBに保存して2回目以降表示されないようにしています。

 

3,プレイリスト学習機能

 

本システムではLast.fmAPIを使用していますが、現状類似曲のデータが少ない問題があります。そこで、再生用プレイリストとして承認されたプレイリストを1つの固まりとみなして、Word2Vecを用いた学習を行う事にしました。

 

from flask import Flask, session, request, redirect, render_template, url_for
from spotify_token import Spotify_token
from gmusicapi import Mobileclient
import connect_lastfm_api
import sqlite3
import urllib.parse
import spotipy
import create_playlist_to_Spotify_etc
import spotify_auth

dbname = "account.db"

app = Flask(__name__)
last_fm_api_key = ""
app.config['SECRET_KEY'] = 'The secret key which ciphers the cookie'


@app.route("/")
def route():
return render_template('login.html')

@app.route("/create_account")
def create_account():
sql = "INSERT INTO account (user_name, password) values (?, ?)"
return render_template('create_account.html')

@app.route("/account_insert", methods=["GET", "POST"])
def account_insert():
res = request.form
user_name = res["user_name"]
password = res["password"]
sql = "INSERT INTO account (user_name, password) values (?, ?)"
data = (user_name, password)
conn = sqlite3.connect(dbname)
conn.cursor()
conn.execute(sql, data)
conn.commit()
conn.close()
return redirect(url_for("main_menu"))

@app.route("/error")
def error():
return render_template("error.html")

@app.route("/main_menu", methods=["GET", "POST"])
def main_menu():
res = request.form
user_name = res["user_name"]
password = res["password"]
if user_name is None or password is None:
return redirect(url_for("error"))
data = (user_name, password)
conn = sqlite3.connect(dbname)
cur = conn.cursor()
cur.execute("SELECT * FROM account WHERE user_name = ? AND password = ?", data)
list = cur.fetchall()
conn.close()
if list == []:
return redirect(url_for("error"))
else:
ID = list[0][0]
session["name"] = ID
return render_template('main_menu.html')

@app.route("/logout")
def logout():
session.pop("name", None)
return render_template("logout.html")

@app.route("/settings")
def settings():
return render_template('settings.html')

@app.route("/spotify_setting")
def spotify_setting():
return render_template("spotify_setting.html")

@app.route("/spotify_auth", methods=["GET", "POST"])
def spotify_auth():
res = request.form
print(res)
user_name = res["user_name"]
ID = session["name"]
service_name = "Spotify"
conn = sqlite3.connect(dbname)
cur = conn.cursor()
q = (ID, service_name)
cur.execute("SELECT * FROM service_account WHERE user_id = ? AND service = ?", q)
list = cur.fetchall()
if list == []:
url1 = "https://accounts.spotify.com/authorize?response_type=code&client_id="
my_client_id = "5e8438676fd741fd98475083c009b373"
url2 = "&scope="
scope = "user-read-private user-read-email"
scope = urllib.parse.quote(scope)
url3 = "&redirect_uri="
redirect_uri = "https://lastfm-playlist-generator-beta.herokuapp.com/"
redirect_uri = urllib.parse.quote(redirect_uri)
call_url = url1 + my_client_id + url2 + scope + url3 + redirect_uri
return redirect(url_for(call_url))

@app.route("/google_setting")
def google_setting():
return render_template("google_setting.html")

@app.route("/google_auth", methods=["GET", "POST"])
def google_auth():
res = request.form
gmail = res["gmail"]
password = res["password"]
ID = session["name"]
service_name = "Google"
conn = sqlite3.connect(dbname)
cur = conn.cursor()
q = (ID, service_name)
cur.execute("SELECT * FROM service_account WHERE user_id = ? AND service = ?", q)
list = cur.fetchall()
if list == []:
android_id = Mobileclient.FROM_MAC_ADDRESS
api = Mobileclient()
logged_in = api.login(email=gmail, password=password, android_id=android_id, locale="ja")
if logged_in == True:
data = (ID, service_name, gmail, password)
cur.execute("INSERT INTO service_account(user_id, service, user_name, password)VALUES(?, ?, ?, ?)", data)
conn.commit()
conn.close()
return redirect(url_for("account_success"))
else:
conn.close()
return redirect(url_for("account_faild"))
else:
conn.close()
return redirect(url_for("account_faild"))

@app.route("/account_success")
def account_success():
return render_template("account_success.html")

@app.route("/account_faild")
def account_faild():
return render_template("account_faild.html", list=list)

@app.route("/input_artist")
def input_artist():
return render_template('input_artist.html')

@app.route("/song_list", methods=["GET", "POST"])
def song_list():
res = request.form
artist = res["artist"]
session["artist"] = artist
session["count"] = 1
list = connect_lastfm_api.get_search_song_list(artist, last_fm_api_key, 1)
return render_template('song_list.html', list=list, number=len(list))

@app.route("/song_list/re", methods=["GET", "POST"])
def re_song_list():
artist = session["artist"]
count = session["count"]
count = count + 1
session["count"] = count
list = connect_lastfm_api.get_search_song_list(artist, last_fm_api_key, count)
return render_template('song_list.html', list=list, number=len(list))

@app.route("/playlist_generate", methods=["GET", "POST"])
def playlist_generate():
res = request.form
artist = session["artist"]
song = res["song"]
playlist = []
first_input = (artist, song)
playlist.append(first_input)
conn = sqlite3.connect(dbname)
cur = conn.cursor()
ID = session["name"]
service = "Last.fm"
q = (ID, service)
cur.execute("SELECT * FROM service_account WHERE user_id = ? AND service = ?", q)
list = cur.fetchall()
conn.close()
count = 0
playlist = genarate_playlist(artist, song, playlist, count)
session["playlist"] = playlist
return render_template("playlist_generate.html", playlist=playlist, number=len(playlist))

@app.route("/playlist_generate/re", methods=["GET", "POST"])
def replaylist_generate():
conn = sqlite3.connect(dbname)
cur = conn.cursor()
ID = session["name"]
res = request.form.getlist
track_list = res("track")
before_playlist = session["playlist"]
dislike_list = []
for i in range(len(track_list)):
track_number = track_list[i]
track_number = int(track_number)
dislike_list.append(before_playlist[track_number])
for j in range(len(dislike_list)):
remove_track = dislike_list[j]
before_playlist.remove(remove_track)
artist = remove_track[0]
song = remove_track[1]
data = (ID, artist, song)
cur.execute("INSERT INTO dislike_track(user_id, artist, song)VALUES(?, ?, ?)", data)
conn.commit()
session["playlist"] = before_playlist
playlist = before_playlist
track = playlist[len(playlist)-1]
artist = track[0]
song = track[1]
count = 0
playlist = genarate_playlist(artist, song, playlist, count)
session["playlist"] = playlist
return render_template("playlist_generate.html", playlist=playlist, number=len(before_playlist))



def genarate_playlist(artist, song, playlist, count):
song_count = len(playlist)
conn = sqlite3.connect(dbname)
cur = conn.cursor()
service = "Last.fm"
ID = session["name"]
q = (ID, service)
cur.execute("SELECT * FROM service_account WHERE user_id = ? AND service = ?", q)
list = cur.fetchall()
if list != []:
user_name = list[0][2]
recent_track_list = connect_lastfm_api.recent_play_song(last_fm_api_key, user_name)
else:
recent_track_list = []
similar_track_list = connect_lastfm_api.get_similar_track(artist, song, last_fm_api_key)
playlist_limit = 15
cur.execute("SELECT artist, song FROM dislike_track WHERE user_id = ?", (ID, ))
dislike_list = cur.fetchall()
if len(similar_track_list) == 0:
track = (artist, song)
if track in playlist:
pass
else:
playlist.append(track)
if len(playlist) >= playlist_limit:
get_playlist(playlist)
else:
page = 1
try:
list = connect_lastfm_api.get_search_song_list(artist, last_fm_api_key, page)
song_list = list[count]
song = song_list["name"]
count = count + 1
genarate_playlist(artist, song, playlist, count)
except KeyError:
artist = connect_lastfm_api.similar_artist_search(artist, last_fm_api_key)
count = 0
genarate_playlist(artist, song, playlist, count)
except IndexError:
artist = connect_lastfm_api.similar_artist_search(artist, last_fm_api_key)
count = 0
genarate_playlist(artist, song, playlist, count)
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
elif track in dislike_list:
continue
else:
playlist.append(track)
if song_count == len(playlist):
similar_artist_list = connect_lastfm_api.get_similar_artist_list(artist, last_fm_api_key)
artist = similar_artist_list[0]
song_list = connect_lastfm_api.get_search_song_list(artist, last_fm_api_key, 1)
for i in range(len(song_list)):
list = song_list[i]
song = list["name"]
track = (artist, song)
if playlist not in track and recent_track_list not in track and dislike_list not in track:
break
else:
continue
count = count + 1
genarate_playlist(artist, song, playlist, count)
elif 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, count)
else:
get_playlist(playlist)
return playlist

def get_playlist(playlist):
return playlist

@app.route("/playlist_generate/on_play", methods=["GET", "POST"])
def on_play_playlist():
playlist = session["playlist"]
ID = session["name"]
conn = sqlite3.connect(dbname)
cur = conn.cursor()
cur.execute("SELECT * FROM service_account WHERE user_id = ?", (ID, ))
account_list = cur.fetchall()
service_list = []
google_list = []
spotify_list = []
for i in range(len(account_list)):
account = account_list[i]
service = account[1]
user_name = account[2]
password = account[3]
service_list.append(service)
if service == "Spotify":
spotify_list.append(user_name)
elif service == "Google":
google_list.append(user_name)
google_list.append(password)
else:
continue
if service_list.count("Google") != 0 and service_list.count("Spotify") != 0:
return redirect(url_for("playlist_generate_which"))
elif service_list.count("Spotify") != 0:
result = get_empty_Spotify_playlist(first_input, playlist)
elif service_list.count("Google") != 0:
gmail = google_list[0]
g_password = google_list[1]
result = get_empty_google_music_playlist(playlist, gmail, g_password)
else:
return redirect(url_for("playlist_generate_end"))
if result is not None:
frg = 1
else:
frg = 0
print(frg)
session["frg"] = frg
return redirect(url_for("result"))


def get_empty_google_music_playlist(not_append_list, gmail, 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)
results = search_google(not_append_list, playlist_id, api)
return results

def search_google(playlist, playlist_id, api):
song_ids = []
not_append_list = []
file = open("playlist_log.txt", mode="a", encoding="utf-8")
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
learn_str = s_artist + "_" + s_song
learn_str_en = urllib.parse.quote(learn_str)
file.write(learn_str_en)
file.write(" ")
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"]
similar_artist_list = connect_lastfm_api.get_similar_artist_list(s_artist, last_fm_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:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
file.write("\n")
file.close()
results = google_playlist_input(playlist_id, song_ids, not_append_list, api)
return results

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)
if len(not_append_list) != 0:
print("以下のリストの曲はSpotify及びGoogle Play Musicで配信されていません")
print(not_append_list)
session.pop("playlist", None)
session["not"] = not_append_list
return results

@app.route("/result")
def result():
not_append_list = session["not"]
frg = session["frg"]
if frg == 0:
msg = "プレイリストが作成出来ませんでした"
else:
msg = "プレイリストが作成されました"
if len(not_append_list) == 0:
number = 0
msg2 = ""
else:
number = len(not_append_list)
msg2 = "以下の曲はストリーミング配信されていません"
return render_template("playlist_generate_success.html", not_append_list=not_append_list,
number=number, msg=msg, msg2=msg2)

if __name__ == "__main__":
# webサーバー立ち上げ
app.run()

 

 

では、プレイリストを乃木坂46シンクロニシティで作成してみます。

f:id:Psyduck_take_it_easy:20180531221548p:plain

f:id:Psyduck_take_it_easy:20180531221634p:plain

f:id:Psyduck_take_it_easy:20180531221750p:plain

最終的に調整をして以下のようにしました。

f:id:Psyduck_take_it_easy:20180531222013p:plain

f:id:Psyduck_take_it_easy:20180531225901p:plain

 

現状、Googleアカウントとしか連携出来ていませんが、プレイリスト作成には成功しています。

f:id:Psyduck_take_it_easy:20180531231517j:plain

 

 

「tkinter」を使ってみる

早速、以下のようなものを書いてみました。

 

import tkinter as tk


def clicked():
print("clicked")

def setting():
root.title("初期設定")
root.geometry()
button1_0 = tk.Button(root, text="Spotifyアカウント連携")
button1_0.pack()

def menu(root):

root.title("メインメニュー")
root.geometry()

button0 = tk.Button(root, text="プレイリスト生成", command=clicked)
button1 = tk.Button(root, text="初期設定", command=setting)

button0.pack()
button1.pack()

root.mainloop()

if __name__ == '__main__':
root = tk.Tk()
menu(root)

 

 

実行するとこのようなものが出てきました。

 

f:id:Psyduck_take_it_easy:20180514225357p:plain

 

「初期設定」にはボタン遷移を設定したのでクリックすると以下のようになりました。

 

f:id:Psyduck_take_it_easy:20180514225540p:plain

 

上の2つのボタンは画面から消えることを想定しましたが上手く出来ませんでした。

 

もう少し、色々と調べて見ようと思います。

 

逆引きPython標準ライブラリ 目的別の基本レシピ180+! (impress top gear)

逆引きPython標準ライブラリ 目的別の基本レシピ180+! (impress top gear)

 

 

社内LTで発表してきました

昨日、継続して製作してきたプレイリスト作成モジュールについて発表してきました。

 

社内でも好評を得て自分としては発表して良かったと思っています。

 

ストリーミング配信サービスが乱立している中で、最適なサービスを選べていないヒトは一定数いるのかなと感じました。よって、統合されたロジックを用いて曲を推薦して各サービスへの橋渡しをするアプリを目指して、まずはGUIで動くようにしてみようと考えています。

 

まずは、「Tkinter」あたりで頑張ってみたいと思います。

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」に関しては配信されていないようです。詳細は不明です。

 

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

日本語表記とローマ字表記の変換が難しい

SpotifyAPIを叩いて曲を取得していますが、返ってくるレスポンスにクセがありました。アーティスト名は英語で返ってきており、曲名は日本語で返ってきているのです。

 

問答無用で最初に上がってきた曲を入れれば良いという案もありますが、これでは、配信していないアーティストの場合オルゴール曲が入ってしまうので、適切ではありません。

 

import spotipy
import re
import connect_lastfm_api
import connect_sandbox
import lev_distance
from spotify_token import Spotify_token

api_key =
username =

def input_artist():
#artist = input("検索したいアーティスト名を入力して下さい")
artist = "乃木坂46"
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)")
yes_or_no = "y"
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("曲名を入力して下さい")
song = "制服のマネキン"
create_playlist(artist, song)

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

def genarate_playlist(artist, song, playlist, first_input, count):
similar_track_list = connect_lastfm_api.get_similar_track(artist, song, api_key)
print(similar_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)
except KeyError:
artist = connect_lastfm_api.similar_artist_search(artist, api_key)
count = 0
genarate_playlist(artist, song, playlist, first_input, count)
except IndexError:
artist = connect_lastfm_api.similar_artist_search(artist, api_key)
count = 0
genarate_playlist(artist, song, playlist, first_input, count)
else:
recent_track_list = connect_lastfm_api.recent_play_song(api_key)
print(recent_track_list)
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)
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)
similar_artist_list = connect_sandbox.get_similar_artist_list(s_artist, api_key)
for item in result["tracks"]["items"]:
print(item)
artist_url = item["artists"][0]
artist_url = artist_url["external_urls"]["spotify"]
print(artist_url)
api_artist = item["artists"][0]["name"]
print(api_artist)
api_song = item["name"]
api_song = re.sub("\t", "", api_song)
print(api_song)
if api_artist == s_artist:
print("OK")
song_id = item["id"]
song_ids.append(song_id)
elif api_artist in similar_artist_list:
if api_song == s_song:
print("OK")
song_id = item["id"]
song_ids.append(song_id)
else:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
else:
word_distance = lev_distance.calc_distance(api_artist, s_artist)
if word_distance > 9:
not_append_song = (s_artist, s_song)
not_append_list.append(not_append_song)
else:
print("OK")
song_id = item["id"]
song_ids.append(song_id)
playlist_input(playlist_id, song_ids, not_append_list)

def 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で配信されていないか、入力されたキーワードが違う可能性があります。")
print(not_append_list)
else:
print("Can't get token for", username)

if __name__ == '__main__':
input_artist()

 

SpotifyAPIから返ってくるレスポンスのアーティスト名と、実際に調べたいアーティスト名のレーベンシュタイン距離を測り、そのスコアで分類しようと考えました。

 

実際のレーベンシュタイン距離の計算は以下の本を参考にしました。

 

 

しかし、結果として特に個人名のローマ字から漢字表記の距離が長く、短い距離でオルゴール曲が混ざってしまう事がわかり、残念ながら使えない事が判明しました。

 

一つ一つ、DBに登録して変換させるように対応しないといけないかもしれないですね… 

 

「Speee もくもく会 #39」に行って作業してきました

4月28日に「Speee もくもく会 #39」に行って先頃から作成しているSpotifyのプレイリスト自動生成モジュールの作業をしてきました。

 

speee.connpass.com

 

以前より気になっていたイベントですが、初参加枠がすぐ埋まってしまうのでなかなか参加出来ずにいました。今回初めて参加する事が出来ました。会場はその名の通り、Speeeさんのオフィスでした。オフィスには多くの本が並んでおり、勉強熱心な方には良いオフィスだと思いました。

 

 

また折りをみて参加したいと思います。