Skip to content

Commit

Permalink
apply isort and black
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Apr 8, 2023
1 parent b227ce1 commit cf353e8
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 126 deletions.
11 changes: 7 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dynamic = ["version", "readme"]
dev = ['pre-commit', 'flake8', 'yapf', 'coverage', 'sphinx', 'sphinx-rtd-theme']

[project.scripts]
ytmusicapi = "spotify_to_ytmusic.main:main"
spotify_to_ytmusic = "spotify_to_ytmusic.main:main"

[project.urls]
homepage = "https://github.com/sigma67/spotify_to_ytmusic"
Expand All @@ -39,9 +39,12 @@ include-package-data=false
[tool.setuptools.package-data]
"*" = ["**.example", "**.py"]

[tool.yapf]
column_limit = 99
split_before_arithmetic_operator = true
[tool.isort]
profile = "hug"
src_paths = ["spotify_to_ytmusic"]

[tool.black]
src_paths = ["spotify_to_ytmusic"]

[tool.coverage.run]
command_line = "-m unittest discover tests"
19 changes: 11 additions & 8 deletions spotify_to_ytmusic/Setup.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import sys

from settings import Settings
import ytmusicapi

from spotify_to_ytmusic.settings import Settings

settings = Settings()


def setup():
choice = input("Choose which API to set up\n"
"(1) Spotify\n"
"(2) YouTube\n"
"(3) both")
choices = ["1","2","3"]
choice = input(
"Choose which API to set up\n" "(1) Spotify\n" "(2) YouTube\n" "(3) both"
)
choices = ["1", "2", "3"]
if choice not in choices:
sys.exit("Invalid choice")

Expand All @@ -22,10 +23,12 @@ def setup():
setup_spotify()
setup_youtube()


def setup_youtube():
settings['youtube']['headers'] = ytmusicapi.setup_oauth()
settings["youtube"]["headers"] = ytmusicapi.setup_oauth()
settings.save()


def setup_spotify():
pass
#settings['spotipy']
# settings['spotipy']
73 changes: 43 additions & 30 deletions spotify_to_ytmusic/SpotifyExport.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,69 @@
from spotipy.oauth2 import SpotifyClientCredentials
import spotipy
import settings
import html
from urllib.parse import urlparse

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

from spotify_to_ytmusic.settings import Settings


class Spotify:
def __init__(self):
conf = settings['spotify']
client_credentials_manager = SpotifyClientCredentials(client_id=conf['client_id'], client_secret=conf['client_secret'])
self.api = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
settings = Settings()
conf = settings["spotify"]
client_credentials_manager = SpotifyClientCredentials(
client_id=conf["client_id"], client_secret=conf["client_secret"]
)
self.api = spotipy.Spotify(
client_credentials_manager=client_credentials_manager
)

def getSpotifyPlaylist(self, url):
playlistId = get_id_from_url(url)
if len(playlistId) != 22:
raise Exception(f'Bad playlist id: {playlistId}')
raise Exception(f"Bad playlist id: {playlistId}")

print("Getting Spotify tracks...")
results = self.api.playlist(playlistId)
name = results['name']
total = int(results['tracks']['total'])
tracks = build_results(results['tracks']['items'])
name = results["name"]
total = int(results["tracks"]["total"])
tracks = build_results(results["tracks"]["items"])
count = len(tracks)
print(f"Spotify tracks: {count}/{total}")

while count < total:
more_tracks = self.api.playlist_items(playlistId, offset=count, limit=100)
tracks += build_results(more_tracks['items'])
tracks += build_results(more_tracks["items"])
count = count + 100
print(f"Spotify tracks: {len(tracks)}/{total}")

return {'tracks': tracks, 'name': name, 'description': html.unescape(results['description'])}
return {
"tracks": tracks,
"name": name,
"description": html.unescape(results["description"]),
}

def getUserPlaylists(self, user):
pl = self.api.user_playlists(user)['items']
pl = self.api.user_playlists(user)["items"]
count = 1
more = len(pl) == 50
while more:
results = self.api.user_playlists(user, offset=count * 50)['items']
results = self.api.user_playlists(user, offset=count * 50)["items"]
pl.extend(results)
more = len(results) == 50
count = count + 1

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

def get_tracks(self, url):
tracks = []
url_parts = parse_url(url)
path = url_parts.path.split('/')
path = url_parts.path.split("/")
id = path[2]
if 'album' == path[1]:
if "album" == path[1]:
album = self.api.album(id)
tracks.extend(build_results(album['tracks']['items'], album['name']))
elif 'track' == path[1]:
tracks.extend(build_results(album["tracks"]["items"], album["name"]))
elif "track" == path[1]:
track = self.api.track(id)
tracks.extend(build_results([track]))
return tracks
Expand All @@ -61,24 +72,26 @@ def get_tracks(self, url):
def build_results(tracks, album=None):
results = []
for track in tracks:
if 'track' in track:
track = track['track']
if not track or track['duration_ms'] == 0:
if "track" in track:
track = track["track"]
if not track or track["duration_ms"] == 0:
continue
album_name = album if album else track['album']['name']
results.append({
'artist': ' '.join([artist['name'] for artist in track['artists']]),
'name': track['name'],
'album': album_name,
'duration': track['duration_ms']/1000
})
album_name = album if album else track["album"]["name"]
results.append(
{
"artist": " ".join([artist["name"] for artist in track["artists"]]),
"name": track["name"],
"album": album_name,
"duration": track["duration_ms"] / 1000,
}
)

return results


def get_id_from_url(url):
url_parts = parse_url(url)
return url_parts.path.split('/')[2]
return url_parts.path.split("/")[2]


def parse_url(url):
Expand Down
76 changes: 20 additions & 56 deletions spotify_to_ytmusic/YouTube.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,38 @@
from ytmusicapi import YTMusic
import os
import re
import difflib
from collections import OrderedDict
import settings

from ytmusicapi import YTMusic

from spotify_to_ytmusic.match import get_best_fit_song_id
from spotify_to_ytmusic.settings import Settings

path = os.path.dirname(os.path.realpath(__file__)) + os.sep


class YTMusicTransfer:
def __init__(self):
self.api = YTMusic(settings['youtube']['headers'], settings['youtube']['user_id'])
settings = Settings()
self.api = YTMusic(
settings["youtube"]["headers"], settings["youtube"]["user_id"]
)

def create_playlist(self, name, info, privacy="PRIVATE", tracks=None):
return self.api.create_playlist(name, info, privacy, video_ids=tracks)

def get_best_fit_song_id(self, results, song):
match_score = {}
title_score = {}
for res in results:
if 'resultType' not in res or res['resultType'] not in ['song', 'video']:
continue

durationMatch = None
if 'duration' in res and res['duration'] and song['duration']:
durationItems = res['duration'].split(':')
duration = int(durationItems[0]) * 60 + int(durationItems[1])
durationMatch = 1 - abs(duration - song['duration']) * 2 / song['duration']

title = res['title']
# for videos,
if res['resultType'] == 'video':
titleSplit = title.split('-')
if len(titleSplit) == 2:
title = titleSplit[1]

artists = ' '.join([a['name'] for a in res['artists']])

title_score[res['videoId']] = difflib.SequenceMatcher(a=title.lower(), b=song['name'].lower()).ratio()
scores = [title_score[res['videoId']],
difflib.SequenceMatcher(a=artists.lower(), b=song['artist'].lower()).ratio()]
if durationMatch:
scores.append(durationMatch * 5)

#add album for songs only
if res['resultType'] == 'song' and res['album'] is not None:
scores.append(difflib.SequenceMatcher(a=res['album']['name'].lower(), b=song['album'].lower()).ratio())

match_score[res['videoId']] = sum(scores) / len(scores) * max(1, int(res['resultType'] == 'song') * 2)

if len(match_score) == 0:
return None

max_score = max(match_score, key=match_score.get)
return max_score

def search_songs(self, tracks):
videoIds = []
songs = list(tracks)
notFound = list()
for i, song in enumerate(songs):
name = re.sub(r' \(feat.*\..+\)', '', song['name'])
query = song['artist'] + ' ' + name
name = re.sub(r" \(feat.*\..+\)", "", song["name"])
query = song["artist"] + " " + name
query = query.replace(" &", "")
result = self.api.search(query)
if len(result) == 0:
notFound.append(query)
else:
targetSong = self.get_best_fit_song_id(result, song)
targetSong = get_best_fit_song_id(result, song)
if targetSong is None:
notFound.append(query)
else:
Expand All @@ -77,7 +42,7 @@ def search_songs(self, tracks):
if i > 0 and i % 10 == 0:
print(f"YouTube tracks: {i}/{len(songs)}")

with open(path + 'noresults_youtube.txt', 'w', encoding="utf-8") as f:
with open(path + "noresults_youtube.txt", "w", encoding="utf-8") as f:
f.write("\n".join(notFound))
f.write("\n")
f.close()
Expand All @@ -91,28 +56,27 @@ def add_playlist_items(self, playlistId, videoIds):
def get_playlist_id(self, name):
pl = self.api.get_library_playlists(10000)
try:
playlist = next(x for x in pl if x['title'].find(name) != -1)['playlistId']
playlist = next(x for x in pl if x["title"].find(name) != -1)["playlistId"]
return playlist
except:
raise Exception("Playlist title not found in playlists")

def remove_songs(self, playlistId):
items = self.api.get_playlist(playlistId, 10000)
if 'tracks' in items:
self.api.remove_playlist_items(playlistId, items['tracks'])
if "tracks" in items:
self.api.remove_playlist_items(playlistId, items["tracks"])

def remove_playlists(self, pattern):
playlists = self.api.get_library_playlists(10000)
p = re.compile("{0}".format(pattern))
matches = [pl for pl in playlists if p.match(pl['title'])]
matches = [pl for pl in playlists if p.match(pl["title"])]
print("The following playlists will be removed:")
print("\n".join([pl['title'] for pl in matches]))
print("\n".join([pl["title"] for pl in matches]))
print("Please confirm (y/n):")

choice = input().lower()
if choice[:1] == 'y':
[self.api.delete_playlist(pl['playlistId']) for pl in matches]
if choice[:1] == "y":
[self.api.delete_playlist(pl["playlistId"]) for pl in matches]
print(str(len(matches)) + " playlists deleted.")
else:
print("Aborted. No playlists were deleted.")

Loading

0 comments on commit cf353e8

Please sign in to comment.