10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ソニックガーデン 若手プログラマAdvent Calendar 2024

Day 6

Spotify APIを使って楽曲レコメンドアプリを作る

Last updated at Posted at 2024-12-05

前置き

こちらの記事はソニックガーデン 若手プログラマ Advent Calendar 2024の6日目の記事です。

はじめに

社内で月一で開催されている若手ハッカソンで「YourDJ」という楽曲レコメンドアプリを作成しました。
楽曲を入力すると、同じアーティストの楽曲を似ている順に並べてお勧めしてくれるアプリです。
これから解説に入るのですが、まずは一度触ってみて欲しいです。
https://music-suggestion-shy-cherry-6079.fly.dev/

※注意

2024/11/27のAPIの仕様変更によって、SpotifyAPIではAudio Featureの取得ができなくなってしまいました。
https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api
このアプリへの影響は以下のとおりです。

  • 楽曲情報の取得はできるので、TOPページから楽曲検索までは可能です
  • 楽曲選択以降の機能が、Audio Featureを用いているのでエラーが出るようになってしまっています

communityでは以下のように、今回制限された機能の復活を求める声が多数投稿されています。
https://community.spotify.com/t5/Spotify-for-Developers/Changes-to-Web-API/td-p/6540414

もし、この声が反映され機能制限が解除されることがあれば、追記という形で修正をしていこうと考えています。
昔のSpotify APIではこのようなことができた、という感じでご覧いただければ幸いです。

ありし日の挙動を画像で紹介

1, 検索したい楽曲名を入力します

2, 候補の中から楽曲を選び「この曲にする」ボタンを押す

3, 同じアーティストの似ている楽曲をDJが紹介してくれる!
(アーティストが同名の楽曲を別バージョンで登録していると、今回のケースのように同名楽曲が出てくることがあります。)

背景

私は普段Spotifyを使って音楽を聴いています。
ただ、好きなアーティストを知った時に、そのアーティストの他の好みの曲を探すことが難しいという困り事がありました。
Spotifyではこのような時に、「人気曲」で人気順に同アーティストの曲を聴くくらいしか手段がなく、次の手段としては「アルバムジャケットでピンときたものを聴く」が来るくらいです。
確かにSpotifyにはオススメ機能はあるにはあるのですが、アーティスト関係なしに似ている曲をお勧めしてくる感じで、今回の要望にはうまく使えません。
そこで、手軽に同じアーティストの似ている曲を探すアプリは作れないかということで、今回のアプリ制作に至りました。

仕組み

使用技術

  • Ruby on Rails
  • rspotify

SpotifyAPIについて

SpotifyではAPIを利用して楽曲の検索やデータの取得ができるようになっています。
そしてSpotifyは楽曲ごとにパラメータを持っています。
例えば次の楽曲の場合、以下のようなパラメータを持っています。

属性
Acousticness 0.0013
Analysis URL https://api.spotify.com/v1/audio-analysis/4a48lWUd64bZgHUDx0GZlj
Danceability 0.551
Duration (ms) 305000
Energy 0.919
External URLs None
Href None
ID 4a48lWUd64bZgHUDx0GZlj
Instrumentalness 0.00638
Key 7
Liveness 0.352
Loudness -3.484
Mode 0
Speechiness 0.074
Tempo 157.989
Time Signature 4
Track URL https://api.spotify.com/v1/tracks/4a48lWUd64bZgHUDx0GZlj
Type audio_features
URI spotify:track:4a48lWUd64bZgHUDx0GZlj
Valence 0.652

今回私はこの中でも以下のパラメータに着目しました

属性 説明
tempo トラックの全体的なテンポの推定値(1分あたりのビート数 (BPM))。
energy energyは 0.0 から 1.0 までの尺度で、強度と活動の知覚的尺度を表します。通常、energyのあるトラックは速く、大きく、騒々しいと感じられます。たとえば、デスメタルはenergyが高く、バッハのプレリュードはスケール上で低いスコアになります。この属性に寄与する知覚的特徴には、ダイナミックレンジ、知覚される音量、音色、オンセットレート、および一般的なエントロピーが含まれます。
valence トラックによって伝えられる音楽的なポジティブさを表す 0.0 から 1.0 までの尺度。高い値を持つトラックはよりポジティブに聞こえ (例: 幸せ、陽気、多幸感)、低い値を持つトラックはよりネガティブに聞こえます (例: 悲しい、落ち込んだ、怒っている)。
danceability danceabilityは、テンポ、リズムの安定性、ビートの強さ、全体的な規則性などの音楽要素の組み合わせに基づいて、トラックがダンスにどれだけ適しているかを表します。値が 0.0 の場合、danceabilityは最も低く、値が 1.0 の場合、danceabilityは最も高くなります。

(https://developer.spotify.com/documentation/web-api/reference/get-audio-features)
から和訳

ここで私は、これらの値が似ている楽曲を同じアーティストの楽曲から取得すれば、好みの曲に素早く出会えるのではと考えました。

実装

今回はRSpotifyというgemを用いて、そのAPIをrubyで使っていくことにします。
https://github.com/guilhermesad/rspotify
RSpotifyはSpotify Web APIのwrapperです。

全体像

アプリのメイン機能が集約されているTrackモデルの全体像が以下です。

class Track < ApplicationRecord
  require 'rspotify'

  def self.get_track(track_name)
    RSpotify.authenticate("<your_client_id>", "<your_client_secret>")
    RSpotify::Track.search(track_name, limit: 20)
  end
  
  def self.get_related_track_ids(artist_name, base_track_id)
    RSpotify.authenticate("<your_client_id>", "<your_client_secret>")
    base_track = RSpotify::AudioFeatures.find(base_track_id)
    target_tracks = RSpotify::Base.search("artist:#{artist_name}", 'track', limit: 50)
    track_ids = target_tracks.map {|track| track.id}
    audio_features = RSpotify::AudioFeatures.find(track_ids)
    (audio_features.map {|track| [track.id, Track.calc_difference(base_track, track)]}).sort_by{|x| x[1] }
  end

  def self.calc_difference(base, target)
    difference = 0
    difference += (base.tempo - target.tempo).abs / [base.tempo, target.tempo].max
    difference += (base.energy - target.energy).abs
    difference += (base.valence - target.valence).abs
    difference += (base.danceability - target.danceability).abs

    difference / 4.0 * 100
  end
end

認証

RSpotifyでは一部のデータにアクセスするには認証が必要です。以下のページからclient_id, client_secretを取得しましょう。
https://developer.spotify.com/my-applications

そしてこのように記述することで、認証が完了します

RSpotify.authenticate("<your_client_id>", "<your_client_secret>")

楽曲の検索

下記のコードで楽曲を検索することができます。今回は20件の候補を取得すれば十分なので、limit: 20としておきます。

RSpotify::Track.search(track_name, limit: 20)

楽曲の表示

そして、spotifyでは共有用の埋め込みが用意されています。
今回は詳しく説明しませんが、viewで以下のようなiframeを表示し、srcの値に楽曲のIDを動的に渡してあげれば、簡単にプレビューやジャケット写真を見せることができます。

%iframe{allow: "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture", allowfullscreen: "", frameborder: "0", height: "80", loading: "lazy", src: "https://open.spotify.com/embed/track/[楽曲のID]?utm_source=generator", style: "border-radius:12px", width: "100%"}

楽曲データの取得

ユーザが選んだ楽曲のアーティスト名をartist_nameとして次のようなコードを実行します。
本当はアーティストのすべての楽曲から取得したいのですが、API制限があり、一回で50曲までしか取得できません。そのため、RSpotify::Base.searchではlimit: 50として限界量まで楽曲を取得することとします。
そして、結果から楽曲のidを取得し、RSpotify::AudioFeatures.findでそれらの楽曲データを取得します。

target_tracks = RSpotify::Base.search("artist:#{artist_name}", 'track', limit: 50)
track_ids = target_tracks.map {|track| track.id}
audio_features = RSpotify::AudioFeatures.find(track_ids)

似ている楽曲順にソートする

ユーザが選んだ楽曲のIDをbase_track_idとして、次のコードを実行します。
類似度の判定にはTrackモデルに定義したcalc_differenceメソッドを用います。
baseにはユーザが選んだ比較元の楽曲を、targetには取得してきた比較したい楽曲を渡します。
パラメータごとに差を絶対値で取って、違いの度合いを計算します。
tempo以外の項目は0~1なのですが、tempoはそれよりも大きい値となるので、影響度を考慮して0~1の値をとるように処理します。
そして、平均値を取り、%の形で示せるように100倍にします。
これで、「不一致度」の値が取れたので、値が小さい(一致している)順に並び替えます。

base_track = RSpotify::AudioFeatures.find(base_track_id)
(audio_features.map {|track| [track.id, Track.calc_difference(base_track, track)]}).sort_by{|x| x[1] }
track.rb
def self.calc_difference(base, target)
  difference = 0
  difference += (base.tempo - target.tempo).abs / [base.tempo, target.tempo].max
  difference += (base.energy - target.energy).abs
  difference += (base.valence - target.valence).abs
  difference += (base.danceability - target.danceability).abs

  difference / 4.0 * 100
end

結果の表示

これで、同アーティストの中で似ている楽曲を取得できました。
実はこのロジックだと、ユーザが選んだ楽曲と同じ曲が100%一致として出てきてしまうので、100%一致している楽曲を弾いて、それ以降の楽曲をお勧め楽曲として並べます。

終わりに

このロジックで楽曲レコメンドアプリを作ることができました。
説明してしまえばそこまで大した仕組みではないのですが、それなりの精度で似ている曲をお勧めしてくれると思います。

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?