Spotifyでいいねした曲をYoutube Musicでいいねするヤツ



# 概要
内容はタイトルママ
SpotifyAPIとYouTubeMusicAPI使う
YouTubeMusicAPIは非公式なので一応注意

spotifyAPIに関しては資料が多いので省略

https://qiita.com/nixo/items/d1602d0817a9633831fd

この記事がわかりやすいのでおすすめ
YouTubeMusicAPIは資料少ないので下の方で解説する

# 環境
* Python 3.9.7
* ytmusicapi==1.1.1
* spotipy==2.23.0

# 処理の流れ
spotifyAPIでいいねした曲を取得したあと、YouTubeMusicAPIで検索して曲名とidを抽出
jsonにspotifyの曲名、ytmusicの検索曲名、ytmusicの検索曲idを保存
json内容を確認してもらい(検索結果が間違ってる可能性があるので)、問題なかったらjsonを元にいいねする関数を起動して終わり
### 処理順
1. spotifyAPI(spotipy)でいいねした曲の、曲名＆アーティスト名を取得する
1. 取得した曲名＆アーティスト名をYouTubeMusicAPI(ytmusicapi)で検索
1. 検索結果から曲名とidを抽出し、spotifyでいいねした曲名と一緒にjsonで保存
1. json内容に問題なかったら、ytmusicで"いいね"する関数を起動して終わり

# コード
```python:main.py
import os
import json
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import ytmusicapi


class Spotify2YTMusic():
    def __init__(self):
        # Spotify APIの設定
        SPOTIFY_CLIENT_ID = 'your client id'
        SPOTIFY_CLIENT_SECRET = 'your client secret'
        SPOTIFY_REDIRECT_URI = 'your redirect uri'
        self.sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=SPOTIFY_CLIENT_ID,
                                                            client_secret=SPOTIFY_CLIENT_SECRET,
                                                            redirect_uri=SPOTIFY_REDIRECT_URI,
                                                            scope='user-library-read'))
        # YTMusicの設定
        # browser.jsonファイルが存在しなかったら作成
        if not os.path.exists('browser.json'):
            ytmusicapi.setup_oauth(filepath="browser.json")
        
        # 作成したbrowser.jsonファイルで設定
        self.ytmusic = ytmusicapi.YTMusic("browser.json")

        # combined_data.jsonファイルが存在するかチェック
        if os.path.exists('combined_data.json'):
            # combined_data.jsonファイルが存在する場合はデータをロードする
            with open('combined_data.json', 'r', encoding='utf-8') as f:
                self.combined_data = json.load(f)
        else:
            # combined_data.jsonファイルが存在しない場合は空のリストを作成する
            self.combined_data = []

    # Spotifyでいいねした曲を取得する関数
    def _get_liked_tracks(self):
        liked_tracks = []
        offset = 0
        limit = 50  # 取得する曲の数（50曲ごとにAPIリクエストを送信）
        while True:
            results = self.sp.current_user_saved_tracks(limit=limit, offset=offset)
            for item in results['items']:
                track = item['track']
                liked_tracks.append({
                    'name': track['name'],
                    'artist': track['artists'][0]['name']
                })
                
            # 次のページがある場合はオフセットを更新してループを続ける
            if results['next']:
                offset += limit
            else:
                break
        return liked_tracks

    # ytmusicの検索結果を取得する関数
    def _search_youtube_music(self,query):
        search_results = self.ytmusic.search(query)

        for result in search_results:
            if result['resultType'] != 'song':
                continue
            print(result['title'])
            print(result['resultType'])
            music = {
                        'title': result['title'],
                        'id': result['videoId']
                    }
            print(music)
            return music

    # ytmusicの検索結果をjsonにして出力
    def printJSON(self):
        liked_tracks = self._get_liked_tracks()
        combined_data = []
        for track in liked_tracks:
            yt_top_result = self._search_youtube_music(track['name'] + ' ' + track['artist'])
            combined_data.append({
                'spotify_track': track,
                'youtube_music_track': yt_top_result['title'],
                'youtube_music_id': yt_top_result['id']
            })
        
        # 結果をJSON形式で出力
        with open('combined_data.json', 'w', encoding='utf-8') as f:
            json.dump(combined_data, f, ensure_ascii=False, indent=2)

    # combined_dataのデータを元にytmusicで"いいね"する
    def likeYTMusicSongs(self):
        # combined_dataからidだけ取得
        # いいねした順が逆になってしまうのでreversed()で戻す
        youtube_music_ids = [item["youtube_music_id"] for item in reversed(self.combined_data)]
        for id in youtube_music_ids:
            self.ytmusic.rate_song(videoId=id, rating = 'LIKE')
            print(f"liked {id}")
```
## \_\_init\_\_() 
やってること
1. spotifyapiの認証
1.  ytmusicapiの認証
1.  combined_data変数にcombined_data.jsonをロード
```python
def __init__(self):
        # Spotify APIの設定
        SPOTIFY_CLIENT_ID = 'your client id'
        SPOTIFY_CLIENT_SECRET = 'your client secret'
        SPOTIFY_REDIRECT_URI = 'your redirect uri'
        self.sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=SPOTIFY_CLIENT_ID,
                                                            client_secret=SPOTIFY_CLIENT_SECRET,
                                                            redirect_uri=SPOTIFY_REDIRECT_URI,
                                                            scope='user-library-read'))
        # YTMusicの設定
        # browser.jsonファイルが存在しなかったら作成
        if not os.path.exists('browser.json'):
            ytmusicapi.setup_oauth(filepath="browser.json")
        
        # 作成したbrowser.jsonファイルで設定
        self.ytmusic = ytmusicapi.YTMusic("browser.json")

        # combined_data.jsonファイルが存在するかチェック
        if os.path.exists('combined_data.json'):
            # combined_data.jsonファイルが存在する場合はデータをロードする
            with open('combined_data.json', 'r', encoding='utf-8') as f:
                self.combined_data = json.load(f)
        else:
            # combined_data.jsonファイルが存在しない場合は空のリストを作成する
            self.combined_data = []
```
spotifyAPIについては省略
 SPOTIFY_CLIENT_ID,SPOTIFY_CLIENT_SECRET,SPOTIFY_REDIRECT_URIは自分で発行したやつに変えてください

ytmusicapiの認証はbrower.jsonを使うので、brower.jsonが無い初回は 
```python
# YTMusicの設定
# browser.jsonファイルが存在しなかったら作成
if not os.path.exists('browser.json'):
    ytmusicapi.setup_oauth(filepath="browser.json")
```
でbrower.jsonを作る
```python
# 作成したbrowser.jsonファイルで設定
self.ytmusic = ytmusicapi.YTMusic("browser.json")
```
作ったbrowser.jsonを読み込んでytmusicapiの設定終了
```python
 # combined_data.jsonファイルが存在するかチェック
if os.path.exists('combined_data.json'):
    # combined_data.jsonファイルが存在する場合はデータをロードする
    with open('combined_data.json', 'r', encoding='utf-8') as f:
        self.combined_data = json.load(f)
else:
    # combined_data.jsonファイルが存在しない場合は空のリストを作成する
    self.combined_data = []
```
ytmusicapiで検索した結果と元の曲名を保存するcombined_data.jsonのロード

## printJSON()
やってること
1. _get_liked_tracks()でspotifyでいいねした曲を取得
1. _search_youtube_music()でytmusicの検索結果を取得
1. combined_data.jsonにspotifyの曲名、ytmusicの曲名、ytmusicのidを書き込む
```python
# ytmusicの検索結果をjsonにして出力
def printJSON(self):
    liked_tracks = self._get_liked_tracks()
    combined_data = []
    for track in liked_tracks:
        yt_top_result = self._search_youtube_music(track['name'] + ' ' + track['artist'])
        combined_data.append({
            'spotify_track': track,
            'youtube_music_track': yt_top_result['title'],
            'youtube_music_id': yt_top_result['id']
        })
    
    # 結果をJSON形式で出力
    with open('combined_data.json', 'w', encoding='utf-8') as f:
        json.dump(combined_data, f, ensure_ascii=False, indent=2)
```

## likeYTMusicSongs()
やってること
1. initでロードしたcombined_dataからytmusicのidを抽出(いいね順もコピーするためにreversed()で反転しておく)
1. ytmusic.rate_song(videoId=id, rating = 'LIKE')でytmusicで"いいね"する
```python
# combined_dataのデータを元にytmusicで"いいね"する
def likeYTMusicSongs(self):
    # combined_dataからidだけ取得
    # いいねした順が逆になってしまうのでreversed()で戻す
    youtube_music_ids = [item["youtube_music_id"] for item in reversed(self.combined_data)]
    for id in youtube_music_ids:
        self.ytmusic.rate_song(videoId=id, rating = 'LIKE')
        print(f"liked {id}")
```

# 使い方
```python
spotify2YTMusic = Spotify2YTMusic()
spotify2YTMusic.printJSON()
```
printJSON()を実行するとcombined_data.jsonが出力されるので、検索結果に間違いがなかったら、
```python
spotify2YTMusic.likeYTMusicSongs()
```
でいいね実行して終わり

# 感想
spotifyからytmusicに移行するとき、700曲あるspotifyのいいねも移行するのがめんどくさいと思ったのが作るきっかけ
そういうサービスはすでにあるっぽいけど、自分の場合だとエラー起きたし簡単に作れそうだから作った

今回はytmusicapiがあったから簡単に作れたけど、全部自力でやったらかなりめんどくさそうだなと感じた
ytmusicapi様々

音楽系サブスクはアーティストのフォローもレコメンドに関わってくるので（多分）、アーティストのフォローを移行する機能もついでに作ったら良かったかも

# 参考資料

https://ytmusicapi.readthedocs.io/en/stable/index.html

https://qiita.com/nixo/items/d1602d0817a9633831fd

https://spotipy.readthedocs.io/en/2.22.1/