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

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

YouTube music がリリースされたので、使ってみる

昨日より、日本でもYouTube musicが使用可能となったので、使ってみました。

 

music.youtube.com

 

私個人は既にGoogle Play Musicの有料版を使っていたので、追加課金なしで使用可能でした。

 

YouTubeが関わっているので、Google Play Musicよりパワーアップしたような気がします。

さて、個人的にはこのYouTube Musicを心待ちにしていました。というのも、Google Play Musicには公式APIはありませんが、YouTubeにはAPIが存在するからです。現段階でYouTube Musicに特化したAPIはありませんが、今後リリースされた場合には再び音楽系のモジュール作りをしたいと思います。

自分に合った技術記事の探し方を考える

またも、久しぶりの更新になってしまいましたが、今日は自分に合った技術記事の探し方について触れたいと思います。

 

自分では自宅・業務問わずGoogle検索にて様々な技術記事に目を通します。ただ、時間の関係上どうしても上位の検索結果に出てくる記事に流れてしまいます。先日、某プログラミングスクールのC言語に関する記事が技術的に間違っていると話題になりました。こちらのブログ記事はGoogle検索には毎回上位に出てきています。しかし、C言語での一件から自分に取っては例のプログラミングの記事が「ノイズ」となってしまいました。よって、現在はドメインを「マイナス検索」して省くようにしていますが、今後ドメインが増えると対処しにくくなります。

 

また、会社ではSlack botが技術記事を自動で持ってくる運用がされていますが、ピンポイントで疑問がある場合にはどうしても検索エンジンを使ってしまいます。

 

検索結果の上位に出そうとするSEO対策を否定するつもりはありません。それは、記事は見て貰わなければ意味が無いので見て貰うための1つの方法だからです。しかし、人によってはSEOによる結果をノイズと感じてしまう場合もあるので、例えば「悪いね」ボタンを検索結果の横に置き、ノイズを手軽に除去出来る仕組みがあればと思います。

 

もし、こういう形で技術記事を集めているという事を教えて下さる方はコメント頂けるとありがたいです。

「エンジニアブログを一年続ける方法」に参加してきました

ちょっと間が空いてしまいましたが、10月3日に「エンジニアブログを一年続ける方法」に参加しました。

 

supporterzcolab.com

 

発表者は以下のブログ主です。

 

willow710kut.hatenablog.com

 

ブログを続けるにあたって、一番大事なのは「無理しない」というのは共感しました。無理しすぎて、力尽きてしまうのは意味が無いので、細くても続けるのが大事なのは改めて感じました。

 

ただ、業務に関するコードは全てGithubのプライベートリポジトリ内なのであんまり書けない気がするので、やっぱりプライベート開発に終始してしまうかもしれないですね。

 

(以上の記事は会場で記入しました。)

「RecoChoku Tech Night #08 - レガシーとの戦い -」に参加してきました

お久しぶりです。また、間が空いてしまいました。中々、発表出来るほどのネタが無くブログの更新が滞ってしまいました。

 

勉強会に行ってきたので、その感想を書きます。

 

7月24日にレコチョクさんで「RecoChoku Tech Night #08 - レガシーとの戦い -」に参加してきました。

 

recochoku.connpass.com

 

レガシーであるの定義はそれぞれありましたが、事情によりサポートが終わったフレームワークを自分達でメンテナンスしながら使い続けるといったお話がありました。

 

また、会社によってはチーム内で組織が閉じてしまい、あまり交流がないというお話もありました。

 

セキュリティ的な問題からサポートを切ってしまうのは簡単ですが、事情により使い続けないといけないというのは自分にも心当たりがあり参考になりました。

 

レコチョクさんの勉強会では毎回軽食のご用意があり、また社員さんがきさくに話しかけてくれるので、とても良い雰囲気です。

 

今後も継続して行きたいと思います。

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

※ 最新の算出プログラムは以下をご参照ください

 

psyduck-take-it-easy.hatenablog.com

 

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

 

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

 

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

 

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

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

 

 

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

 

www.desktoptetsu.com

d.hatena.ne.jp

 

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

www.geocities.jp

 

今回はこちらのブログで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だった路線等を含めたルートでの最長片道切符のルート算出に挑んでみたいと思います。

 

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

 

mikaka.org

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)