10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Spotifyで聴いた曲を全部プレイリストに保存する

Last updated at Posted at 2021-03-01

はじめに

Spotifyの履歴は50曲しか保存されないし、デスクトップアプリからしか確認できません。プレイリストには1万曲まで追加できるようなので、再生した曲を全部プレイリストに自動追加してくれるスクリプトを作ってHerokuで動かしてみました。
Spotipy

でSpotify APIを叩きます。

今聴いている曲を取得

デベロッパーページ
https://developer.spotify.com/dashboard/applications
にアクセスしてCREATE AN APPからAPPを作成し、クライアントIDとクライアントシークレットを入手します。作ったAPPのEDIT SETTINGSから、Redirect URIsにhttps://example.com/callback/ を登録しておきます。

以下のコードのscopeにはbotの権限を指定します。今回は現在再生している曲を読み出したいのでuser-read-currently-playingplaylist-modify-publicを指定します。非公開プレイリストに曲を追加していきたい場合は代わりにplaylist-modify-privateを指定します。


import spotipy
from spotipy.oauth2 import SpotifyOAuth
import os

# 環境変数を設定する。代わりにbashrcファイルなどで設定してもOK
os.environ["SPOTIPY_CLIENT_ID"] = "クライアントID"
os.environ["SPOTIPY_CLIENT_SECRET"] = "クライアントシークレット"
os.environ["SPOTIPY_REDIRECT_URI"] = "https://example.com/callback/"

scope = "user-read-currently-playing playlist-modify-public"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))

current_playing = sp.current_user_playing_track()
track_name = current_playing['item']['name']
track_id = current_playing["item"]["id"]
artist_name = current_playing['item']['artists'][0]['name']
print(track_name + "/" + artist_name)

このコードを走らせると、Spotifyの認証ページが開くので同意ボタンを押します。"Example Domain"と書かれたページにリダイレクトされるので、そのページのURLをコピーし、

Enter the URL you were redirected to:

と表示しているコンソールにペーストします。今再生している曲名/アーティスト名が表示されれば成功です。

プレイリストに追加

曲を追加したいプレイリストのIDを調べます。プレイリストのシェア用URLはhttps://open.spotify.com/playlist/37i9dQZF1DX7HOk71GPfSwのようになっていますが、最後の37i9dQZF1DX7HOk71GPfSwの部分がプレイリストのIDです。

playlist_id = "プレイリストID"
sp.playlist_add_items(playlist_id, [track_id], position=0)

track_idの曲をプレイリストの先頭に追加することができます。

聴いた曲をプレイリストに追加

スクリプトをwhile Trueでずっと動かし続けるか、cronなどを使って定期的に動かすのかの2通りの方針があります。ローカル環境で実行するなら前者でも大丈夫ですが、Herokuなどで動かす場合は、24時間に一度スクリプトが停止するので後者のほうが良いでしょう。

方法1(While True)

30秒ごとに現在再生している曲を調べて、前回と異なる場合はプレイリストに追加します。

import spotipy
from spotipy.oauth2 import SpotifyOAuth
import time
import os

os.environ["SPOTIPY_CLIENT_ID"] = "クライアントID"
os.environ["SPOTIPY_CLIENT_SECRET"] = "クライアントシークレット"
os.environ["SPOTIPY_REDIRECT_URI"] = "https://example.com/callback/"
playlist_id = "プレイリストID"
scope = "user-read-currently-playing playlist-modify-public"

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
last_track_id = ""
while True:
    try:
        current_playing = sp.current_user_playing_track()
        if current_playing is not None:
            track_name = current_playing['item']['name']
            track_id = current_playing["item"]["id"]
            artist_name = current_playing['item']['artists'][0]['name']
            if last_track_id != track_id:
                last_track_id = track_id
                print(track_name + "/" + artist_name)
                sp.playlist_add_items(playlist_id, [track_id], position=0)
        else:
            print("no song is playing...")
    except Exception as e:
        print(e)
    time.sleep(30)

方法2(定期的に実行)

直近1時間の再生履歴を調べて、プレイリストに追加します。このスクリプトは1時間ごとに定期実行する必要があります。

import spotipy
from spotipy.oauth2 import SpotifyOAuth
import time
import os

os.environ["SPOTIPY_CLIENT_ID"] = "クライアントID"
os.environ["SPOTIPY_CLIENT_SECRET"] = "クライアントシークレット"
os.environ["SPOTIPY_REDIRECT_URI"] = "https://example.com/callback/"
playlist_id = "プレイリストID"
scope = "user-read-currently-playing playlist-modify-public"

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
now_in_ms = int(time.time()*1000) # ミリ秒単位で現在のUINX時刻を取得
one_hour_in_ms = 60*60*1000 #1時間をミリ秒単位で表現

recently_played = sp.current_user_recently_played(after=now_in_ms-one_hour_in_ms)
if recently_played is not None:
	items = recently_played["items"]
	track_ids = [item["track"]["id"] for item in items]
	sp.playlist_add_items(playlist_id, track_ids, position=0)
	print("added:", track_ids)

Herokuで動かす

ローカル環境で実行してもいいですが、24時間動かすならサーバーから実行したほうが便利なので、Herokuの無料枠で動かしてみます。Herokuへのデプロイについては

を参考にしました。requirement.txt

pip3 freeze | grep spotipy > requirements.txt

で作成します。また、

python3 -V

で自分のPythonのバージョンを確認すると3.6.9だったので、Herokuが対応しているPythonのバージョンを

で調べ、同系列の3.6.13runtime.txtに指定しました。

runtime.txt
python-3.6.13

認証の問題

スクリプトを実行してみると、コンソールに

Enter the URL you were redirected to:

が表示され、認証が必要なようです。しかしながら、Herokuからブラウザを開くことができないので、URLを手に入れることができず、困ってしまいました。

調べてみると、GithubのIssue

に解決策がありました。

まずローカルの環境で一度スクリプトを実行し、認証を済ませておきます。すると

{"access_token": "xxxxxxxx", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "xxxxxxxx", "scope": "playlist-modify-public user-read-recently-played", "expires_at": 1615812124}

のような中身のキャシュファイルが.casheにできています。中身を適当なファイルにコピペし、Herokuにpushしておきます。

そして、スクリプトの

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))

cache_handler = spotipy.cache_handler.CacheFileHandler(cache_path="キャッシュファイルへのパス")
auth_manager = SpotifyOAuth(scope=scope, cache_handler=cache_handler,show_dialog=True)
sp = spotipy.Spotify(auth_manager=auth_manager)

に書き換えると、認証しなくても動くようになりました。

Heroku Schedulerのズレ問題

「方法2」のスクリプトをHeroku Schedulerで1時間ごとに実行してみました。しかし、Heroku Schedulerの実行時間に1~2分のズレがあり、その間の履歴を取り逃してしまうという問題が生じました。そこで、毎時10分にスケジュールしておき、例えばx時11分に実行されたならx-1時0分0秒からx時0分0秒までの履歴を取得することで解決しました。
以下のスクリプトを毎時10分に自動実行しています。

# 毎時10分に実行
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import time, os
from datetime import datetime

os.environ["SPOTIPY_CLIENT_ID"] = "クライアントID"
os.environ["SPOTIPY_CLIENT_SECRET"] = "クライアントシークレット"
os.environ["SPOTIPY_REDIRECT_URI"] = "https://example.com/callback/"

playlist_id = "プレイリストID"

scope = "user-read-recently-played playlist-modify-public"
cache_handler = spotipy.cache_handler.CacheFileHandler(cache_path="cash_data.txt")
auth_manager = SpotifyOAuth(scope=scope, cache_handler=cache_handler,show_dialog=True)
sp = spotipy.Spotify(auth_manager=auth_manager)

now = datetime.now() # x時10分
oclock = datetime(now.year, now.month, now.day, now.hour,0) # x時0分
one_hour_in_ms = 60*60*1000 #1時間(ミリ秒単位)
before = int(oclock.timestamp()*1000) # x時0分のunix時(ミリ秒単位)
after = before - one_hour_in_ms # x-1時0分のunix時(ミリ秒単位)

# beforeとafterは同時に指定できない(泣)
recently_played = sp.current_user_recently_played(after=after)

# 日時の文字列をミリ秒単位のunix時に変換する関数
def parser(strtime):return int(datetime.strptime(strtime, \
    "%Y-%m-%dT%H:%M:%S.%fZ").timestamp()*1000)
if recently_played is not None:
    items = recently_played["items"]
    track_ids = [item["track"]["id"] for item in items \
    if parser(item["played_at"]) < before]
    sp.playlist_add_items(playlist_id, track_ids, position=0)

#参考にした記事

10
11
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
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?