Skip to content

Commit

Permalink
improve liked implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed May 29, 2023
1 parent 4f2fd95 commit b6f7354
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 60 deletions.
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Setup
For backwards compatibility you can also create your own file and pass it using ``--file settings.ini``.

If you want to transfer private playlists from Spotify (i.e. liked songs), choose "yes" for oAuth authentication, otherwise choose "no".
For oAuth authentication you should set `http://localhost` as redirect URI for your app in Spotify's developer dashboard.
For oAuth authentication you should set ``http://localhost`` as redirect URI for your app in Spotify's developer dashboard.

Usage
------
Expand All @@ -80,13 +80,13 @@ For migration purposes, it is possible to transfer all public playlists of a use
Transfer liked tracks of the Spotify user
-----------------------------------------

**You must you oAuth authentification for transfering liked songs.**
**You must you oAuth authentication for transferring liked songs.**

.. code-block::
spotify_to_ytmusic liked
This command will open browser where you should give access to your account (if you havn't done that before).
This command will open browser where you should give access to your account (if you haven't done that before).
After authorization you will be redirected to localhost, copy link you were redirected to (looks like localhost/?code=...) and paste to command line.

Command line options
Expand Down
54 changes: 26 additions & 28 deletions spotify_to_ytmusic/controllers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import time
from datetime import datetime

import spotipy

from spotify_to_ytmusic.setup import setup as setup_func
from spotify_to_ytmusic.spotify import Spotify
from spotify_to_ytmusic.ytmusic import YTMusicTransfer
Expand All @@ -16,6 +18,13 @@ def _get_spotify_playlist(spotify, playlist):
return


def _print_success(name, playlistId):
print(
f"Success: created playlist '{name}' at\n"
f"https://music.youtube.com/playlist?list={playlistId}"
)


def _init():
return Spotify(), YTMusicTransfer()

Expand All @@ -37,48 +46,37 @@ def all(args):
"PUBLIC" if p["public"] else "PRIVATE",
videoIds,
)
print(playlist_id)
_print_success(p["name"], playlist_id)
except Exception as ex:
print(f"Could not transfer playlist {p['name']}. {str(ex)}")

def liked(args):
spotify, ytmusic = _init()
tracks = spotify.getLikedTracks()
if len(tracks) == 0:
exit("Could not find any liked tracks for the user.")

print(f"{len(tracks)} tracks found. Creating playlist...")
try:
videos_ids = ytmusic.search_songs(tracks)
playlist_id = ytmusic.create_playlist(
"Liked songs (Spotify)",
"your liked tracks from spotify",
"PRIVATE",
videos_ids,
)
print(playlist_id)
except Exception as ex:
print("Could not transfer liked songs. " + str(ex))


def create(args):
spotify, ytmusic = _init()
def _create_ytmusic(args, playlist, ytmusic):
date = ""
if args.date:
date = " " + datetime.today().strftime("%m/%d/%Y")

playlist = _get_spotify_playlist(spotify, args.playlist)
name = args.name + date if args.name else playlist["name"] + date
info = playlist["description"] if (args.info is None) else args.info
videoIds = ytmusic.search_songs(playlist["tracks"])

playlistId = ytmusic.create_playlist(
name, info, "PUBLIC" if args.public else "PRIVATE", videoIds
)
_print_success(name, playlistId)

print(
f"Success: created playlist {name}\n"
f"https://music.youtube.com/playlist?list={playlistId}"
)

def create(args):
spotify, ytmusic = _init()
playlist = _get_spotify_playlist(spotify, args.playlist)
_create_ytmusic(args, playlist, ytmusic)


def liked(args):
spotify, ytmusic = _init()
if not isinstance(spotify.api.auth_manager, spotipy.SpotifyOAuth):
raise Exception("OAuth not configured, please run setup and set OAuth to 'yes'")
playlist = spotify.getLikedPlaylist()
_create_ytmusic(args, playlist, ytmusic)


def update(args):
Expand Down
32 changes: 17 additions & 15 deletions spotify_to_ytmusic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,44 @@ def get_args(args=None):
spotify_playlist = argparse.ArgumentParser(add_help=False)
spotify_playlist.add_argument("playlist", type=str, help="Provide a playlist Spotify link.")

create_parser = subparsers.add_parser(
"create",
help="Create a new playlist on YouTube Music.",
parents=[spotify_playlist],
)
create_parser.set_defaults(func=controllers.create)
create_parser.add_argument(
spotify_playlist_create = argparse.ArgumentParser(add_help=False)
spotify_playlist_create.add_argument(
"-d",
"--date",
action="store_true",
help="Append the current date to the playlist name",
)
create_parser.add_argument(
spotify_playlist_create.add_argument(
"-i",
"--info",
type=str,
help="Provide description information for the YouTube Music Playlist. Default: Spotify playlist description",
)
create_parser.add_argument(
spotify_playlist_create.add_argument(
"-n",
"--name",
type=str,
help="Provide a name for the YouTube Music playlist. Default: Spotify playlist name",
)
create_parser.add_argument(
spotify_playlist_create.add_argument(
"-p",
"--public",
action="store_true",
help="Make created playlist public. Default: private",
)

create_parser = subparsers.add_parser(
"create",
help="Create a new playlist on YouTube Music.",
parents=[spotify_playlist, spotify_playlist_create],
)
create_parser.set_defaults(func=controllers.create)

liked_parser = subparsers.add_parser(
"liked", help="Transfer all liked songs of the user.", parents=[spotify_playlist_create]
)
liked_parser.set_defaults(func=controllers.liked)

update_parser = subparsers.add_parser(
"update",
help="Delete all entries in the provided Google Play Music playlist and update the playlist with entries from the Spotify playlist.",
Expand All @@ -71,11 +78,6 @@ def get_args(args=None):
all_parser.add_argument("user", type=str, help="Spotify userid of the specified user.")
all_parser.set_defaults(func=controllers.all)

liked_parser = subparsers.add_parser(
"liked", help="Transfer all liked songs of the user."
)
liked_parser.set_defaults(func=controllers.liked)

return parser.parse_args(args)


Expand Down
8 changes: 4 additions & 4 deletions spotify_to_ytmusic/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ def setup_youtube():
def setup_spotify():
settings = Settings()
credentials = {
"client_id": input(
"Paste your client id from the Spotify developer dashboard:"
),
"client_id": input("Paste your client id from the Spotify developer dashboard:"),
"client_secret": input("Paste your client secret from the Spotify developer dashboard:"),
"use_oauth": input("Use OAuth method for authorization (required for transfer liked songs) yes/no:"),
"use_oauth": input(
"Use OAuth method for authorization to transfer private playlists (yes/no):"
),
}
settings["spotify"].update(credentials)
settings.save()
Expand Down
22 changes: 15 additions & 7 deletions spotify_to_ytmusic/spotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from urllib.parse import urlparse

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from spotipy.oauth2 import SpotifyOAuth
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOAuth

from spotify_to_ytmusic.settings import Settings

Expand All @@ -23,9 +22,14 @@ def __init__(self):
string.hexdigits
), f"Spotify client_secret not set or invalid: {client_secret}"

use_oauth = conf["use_oauth"] == "y" or conf["use_oauth"] == "yes"
use_oauth = conf.getboolean("use_oauth")
if use_oauth:
auth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri="http://localhost", scope="user-library-read")
auth = SpotifyOAuth(
client_id=client_id,
client_secret=client_secret,
redirect_uri="http://localhost",
scope="user-library-read",
)
self.api = spotipy.Spotify(auth_manager=auth)
else:
client_credentials_manager = SpotifyClientCredentials(
Expand Down Expand Up @@ -70,14 +74,18 @@ def getUserPlaylists(self, user):

return [p for p in pl if p["owner"]["id"] == user and p["tracks"]["total"] > 0]

def getLikedTracks(self):
def getLikedPlaylist(self):
response = self.api.current_user_saved_tracks(limit=50)
tracks = response["items"]
while response["next"] != None:
while response["next"] is not None:
response = self.api.current_user_saved_tracks(limit=50, offset=response["offset"] + 50)
tracks.extend(response["items"])

return build_results(tracks)
return {
"tracks": build_results(tracks),
"name": "Liked songs (Spotify)",
"description": "Your liked tracks from spotify",
}


def build_results(tracks, album=None):
Expand Down
9 changes: 8 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ def test_get_args(self):
args = get_args(["setup"])
self.assertEqual(len(vars(args)), 3)

def test_liked(self):
with mock.patch(
"sys.argv", ["", "liked", "-n", "spotify_to_ytmusic", "-d", "-i", "test liked"]
):
main()

def test_create(self):
with mock.patch("sys.argv", ["", "all", "sigmatics"]):
main()

with mock.patch(
"sys.argv", ["", "create", TEST_PLAYLIST, "-n", "spotify_to_ytmusic", "-i", "test-playlist", "-d"]
"sys.argv",
["", "create", TEST_PLAYLIST, "-n", "spotify_to_ytmusic", "-i", "test-playlist", "-d"],
):
main()

Expand Down
5 changes: 3 additions & 2 deletions tests/test_spotipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@


class TestSpotify(unittest.TestCase):

def setUp(self) -> None:
self.spotify = Spotify()

def test_getSpotifyPlaylist(self):
data = self.spotify.getSpotifyPlaylist("https://open.spotify.com/playlist/03ICMYsVsC4I2SZnERcQJb")
data = self.spotify.getSpotifyPlaylist(
"https://open.spotify.com/playlist/03ICMYsVsC4I2SZnERcQJb"
)
self.assertEqual(len(data), 3)
self.assertGreater(len(data["tracks"]), 190)

Expand Down

0 comments on commit b6f7354

Please sign in to comment.