SpotifyWebAPIで遊んでみる
最近趣味としてのプログラミングというか創作みたいなものができていなかったので久しぶりに遊びました。
SpotifyのAPIは色々頑張っていて、特に audio-features
という曲の特徴(BPMや調はもちろんenergyやdanceabilityなどSpotifyが独自に算出した値など)を取ってこれるのが個人的には面白いな〜と思いました。この曲の特徴を使えば、ちょっとした作業の時やBGMが欲しい時にざっくりと今のフィーリングを指定したら勝手にプレイリスト作ってくれる君ができそう!というのを思い付いたので手を動かしました。
できたもの
"音楽を探す" ことを"dig" という風に言うのでこんな名前です。
技術的な話
使ったフレームワークなど
APIにリクエストを投げたり、ユーザの認証のためにリダイレクトしたりするのでWebサーバが必要。今回はHerokuにデプロイして楽するつもりだったので、WebアプリのフレームワークにはHerokuとの親和性の高いExpressを選びました。Procfileちょろっと書けば、 npm install
とかを勝手にHeroku側でやってくれてとても楽だった。
ユーザ認可(OAuth2.0)
検索やプレイリストを作るためにユーザーのリソースにアクセスする必要があるのでSpotify側で認可が必要です。 この記事を読んでアクセストークンのことを理解した気になったりしました。SpotifyのAPIは Authorization Scopesを指定することでアクセスできるリソースを絞ることができます。取ってきたアクセストークンをどこに置いておくべきかみたいな話は(きっとDBとかサーバ側に置いておいたほうがいいんだろうけど)あまりよく分かっていないのでまた調べよう....。
今回はCookieに持たせていて、その理由としては
DB立てるのが面倒- Scopeを絞っていて、せいぜいプレイリストの作成や追加しかできないトークンなので漏れても破壊的なことはできない
- Spotify側がtokenがexpireする時間を6000秒にしている
というあたりです。Cookie抜いてくるサイトにユーザーが行っちゃたりしたらちょっとアレな実装になっています。これ追々対応すべきかな〜...。
プレイリストを作っていく
何はともあれ、まずは楽曲を探します。Node向けにAPIアクセスのヘルパーパッケージがあったのでそれを使います。
APIには「無作為に曲を取ってくる」というエンドポイントが提供されていないのでちょっと頑張ります。楽曲にはID(22桁のbase-62値)が割り振られていますので、これをランダムに生成して200番が返ってくれば続けて audio-features
を見る、という作戦を考えますた。しかしあまりにも404ばかり返ってくる。まあそれはそうだなということで GET /search
を使うことに。
// 検索クエリに使う文字列候補/ const queryStrs = "abcdefghijklmnopqrstuvwxyzあいうえおかきくけこさしすせそたちってとなにぬねのはひふへほまみむめもやゆよわをん"; let q = queryStrs[getRandomInt(0, queryStrs.length)]; // 今回は1文字だけで検索している spotifyApi .searchTracks(q, { market: "JP", limit: 5, offset: getRandomInt(1, 1000) // offsetをランダムにすることで曲がなるべくかぶらないように }) // 適当な整数を返す function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min)) + min; }
audio-features
はあまりにもデータの種類が多いので今回は3つだけ使うことに。
valence
: 楽曲の明るさ/高ければ高いほど明るく低いほど悲しい雰囲気tempo
: 曲のBPMenergy
: 曲のエナジー(?) fast, loud, and noisy だとこの値が高くなるとのこと
なんかちょっとずつ各要素がかぶってる気もしなくないので、これはちゃんと考えるともっと自分のフィーリングに合った楽曲をお届けできそう。 フォームから受け取った3つの値から許容する誤差範囲を決めて、それに一致する値を持っていればプレイリストに追加します。プレイリストに追加するかどうか判定しているコードがこんな感じ。
function isAddableTrack(feature) { // 許容する誤差の範囲 const tolerance = 0.1; const tempoTolerance = 20; // かなりかわいい実装になってるが、3つの値のうち2つが許容範囲内ならOK let judgeCount = 0; if ( happinessVal / 100 - tolerance < feature.valence && feature.valence < happinessVal / 100 + tolerance ) { judgeCount += 1; } if ( energyVal / 100 - tolerance < feature.energy && feature.energy < energyVal / 100 + tolerance ) { judgeCount += 1; } if ( tempoVal - tempoTolerance < feature.tempo && feature.tempo < tempoVal + tempoTolerance ) { judgeCount += 1; } if (judgeCount >= 2) { return true; } else { return false; } } }
プレイリストに追加する楽曲数はユーザーが指定できたらいいなと思っていたが、あまりに探すのに時間がかかるとHerokuのタイムアウトに引っかかってしまうので今回は6曲以上見つけたらお終いにします。
if (tracks.length > 6) { // playlistの名前をとりあえずタイムスタンプにしている // ゆくゆくはちゃんとフィーリングに沿った名前を自動生成したい let now = new Date(); let ts = dateformat(now, "yyyy/mm/dd HH:MM:ss"); spotifyApi .createPlaylist(userId, ts) .then(data => { let playlistId = data.body.id; let playlistUri = data.body.external_urls.spotify; spotifyApi .addTracksToPlaylist(playlistId, properTracks) .then(data => { // Playlistが正しく作られたらそこに直接リダイレクトする res.redirect(playlistUri); }) .catch(err => { console.log(err); res.send('something wrong!!'); }); }) .catch(err => { console.log(err); res.send('something wrong!!'); });
エラーハンドリングとかがかなり甘めですが、こんな感じです。
もっとやれそうなこと
- 値を極端に静かめにすると「ヒーリングミュージック」ばかりになる
- こんな感じ
- これはこれで面白い
- 歌詞があるかどうか(instrumentalness)とかいう項目もあるのでうまく使えたら良さそう
- ロード画面がしょぼい&ロードが長くて不安になる
- プレイリスト作るのが長いのはHerokuでは限界がある気がする
- 直接プレイリストにリダイレクトしなくてもいい気がする
最後に
SpotifyWebAPI、まだまだ遊べそうという気持ちになりました。その日の天候とか自分の書いた文章とか自撮りした表情とかから感情分析して、プレイリスト作ってみるとかも面白そう。 作業する際やパーティの際にさっとBGM流したいけど、いつも同じ曲ばかり聴いてしまう...何流したらいいか分からない....みたいな方にはオヌヌメです!!随時ブラッシュアップしていくのでよかったら遊んでみてください。 最後に私のハイになった気分によって生成された元気の出るプレイリストを置いておきます!!!
参考にしたサイト
- GitHub - thelinmichael/spotify-web-api-node: A Node.js wrapper for Spotify's Web API.
- Web API Reference | Spotify for Developers
2019.12.10 追記
触ってみたよ!みたいな声いただきました。ありがとうございます。その中でいくつか「エラーになっちゃう」と教えていただきました…ちゃんと見れてないのですが多分Herokuのタイムアウトが原因だろうなと思っています。(プレイリストを作る処理が、条件にマッチする曲がない限り探し続けてしまうのでそこが遅い気がする)その場合裏でプレイリストを作る処理は行われているのでSpotifyのマイページにはプレイリスト追加されていると思います…。ちょっとHerokuに関しては課金するか、別のサーバに移すかどうか考えます!!よろしくお願いします!!
2019.12.10 さらに追記
タイムアウト対策として色々いじってみました。
- APIの制限にかかると思って設定していたsleep処理をギリギリまで短くした
- 最悪の場合1曲でもいいからタイムアウトする前にプレイリストを出力するようにした(setTimeOutで30秒計測している)
これでとりあえずタイムアウトのエラーは減って欲しいな....?
GAE/ Go1.9 から Go1.12 へ移行するためにやったこと
業務でGAEのGoアプリケーション(1.9)を1.12化をすることがあったので、作業ログをまとめておきます。
やること
app.yaml
の書き換えruntime: go112
script: auto
skip_files
は.gcloudignore
へ移行login
が非推奨になるので新しい認証を入れるlogin | 非推奨 | Go 1.12 ラインタイムでは login がサポートされません。ユーザー認証に別の方法を使用する必要があります。
init.go
&init_test.go
の書き換え- ファイル名を
main.go
&main_test.go
に変更 func init()
をfunc main()
に変更
- ファイル名を
ハマったところ
go run main.go
ができない
- localで
go run main.go
してみるとすでにappengineパッケージ周りでエラーが出ていたのでサポート対象外ってAPIが廃止になったてことかな......- ちなみに
dev_appserver.py
みたいなアレが1.12ランタイムから使えなくなったのでそれはそう、という感じ - 完全に appengine package から脱出しないとローカルで実行することができないのでちょっと面倒
- ちなみに
go mod 使うとワーキングディレクトリが go.mod
がある場所になる
- これまでは
app.yaml
のある場所がワーキングディレクトリだった - localでは動くものがデプロイすると
template file not found
になってしまった- ひとまずtemplateファイルの場所をgo.modからの相対パスで指定している
app.yaml
にmain
属性を指定するみたいな方法もあるらしい
internal.flushLog: Flush RPC: service bridge HTTP failed: Post http://appengine.googleapis.internal:10001/rpc_http: dial tcp 169.254.169.253:10001: i/o timeout
というエラー
これは私の手元での問題なのだが、
go mod init
するとGopkg.lock
から依存を解決してくれていたのでしっかり以前のバージョンが更新されないまま入っていた- latestにアップデートしてみて、これで直るか!!と思ったけどダメ
- 検索してみると 同じような症状の人を見つけた
- 原因不明なのでgo1.11ランタイムを使っていますとのことぽい、そんな...
appengineパッケージを完全に脱出すると治った
appengine.Main()
->http.ListenAndServe
appengine.NewContext(req)
->req.Context()
google.golang.org/appengine/datastore
->cloud.google.com/go/datastore
- ちょっとずつ実装が変わっていたのでドキュメント見比べつつ直した
10月には1.9ランタイムがdeprecatedになっているにも関わらずあまりインターネッツに情報がなかったので少し大変だった。とりあえず動いてはいるけど、これでいいのかな?という感じで自信がない...
参考にしたページ
Vuexのstateの変更をVueで監視したい
ここに書いてあるように computed
に指定すればよしなに監視してくれる。
computed: { hoge() { return store.state.hoge; } }
Go で time.Format() する際に 2019 はダメだけど 2006 ならいけた
package main import ( "fmt" "time" ) func main() { t := time.Now() fmt.Println(t.Format("20190709_151230")) // 270790709_1372710 fmt.Println(t.Format("20060709_151230")) // 20190709_1372710 }
何だよ 27079
年て.................。
追記
— micnncim (@micnncim) 2019年7月27日
そうなんだ〜〜〜
macOS 10.15 Beta にしてからbrew upgrade が動かなかったりした
タイトルの通り、新しいもの好きの私はすぐアップデートとかしたがるんですが毎回こうやってBeta版の洗礼を受けます
今回は
brew upgrade
が動かなくなった
==> Upgrading heroku/brew/heroku Error: Your Xcode (10.2.1) is too outdated. Please update to Xcode 11.0 (or delete it). Xcode can be updated from: https://developer.apple.com/download/more/