Skip to content

Commit

Permalink
Merge pull request #22 from Yoween/integration
Browse files Browse the repository at this point in the history
bug fixes and code formatting
  • Loading branch information
linsomniac authored Feb 5, 2024
2 parents 03bbf55 + 7bfbbc1 commit 200097d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist/
spotify2ytmusic/playlists_backup.json
spotify2ytmusic/settings.json
spotify2ytmusic/test.py
.idea/
80 changes: 55 additions & 25 deletions spotify2ytmusic/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ def load_playlists_json(filename: str = "playlists.json", encoding: str = "utf-8
return json.load(open(filename, "r", encoding=encoding))


def create_playlist(pl_name: str):
"""
Create a YTMusic playlist
def create_playlist(pl_name: str) -> None:
""" Create a YTMusic playlist
Args:
`pl_name` (str): The name of the playlist to create. It should be different to "".
"""
yt = get_ytmusic()

Expand All @@ -90,7 +93,7 @@ def iter_spotify_liked_albums(
"""Songs from liked albums on Spotify."""
spotify_pls = load_playlists_json(spotify_playlist_file, spotify_encoding)

if not "albums" in spotify_pls:
if "albums" not in spotify_pls:
return None

for album in [x["album"] for x in spotify_pls["albums"]]:
Expand All @@ -103,7 +106,16 @@ def iter_spotify_playlist(
spotify_playlist_file: str = "playlists.json",
spotify_encoding: str = "utf-8",
) -> Iterator[SongInfo]:
"""Songs from a specific album ("Liked Songs" if None)"""
"""Songs from a specific album ("Liked Songs" if None)
Args:
`src_pl_id` (Optional[str], optional): The ID of the source playlist. Defaults to None.
`spotify_playlist_file` (str, optional): The path to the playlists backup files. Defaults to "playlists.json".
`spotify_encoding` (str, optional): Characters encoding. Defaults to "utf-8".
Yields:
Iterator[SongInfo]: The song's information
"""
spotify_pls = load_playlists_json(spotify_playlist_file, spotify_encoding)

for src_pl in spotify_pls["playlists"]:
Expand Down Expand Up @@ -132,14 +144,22 @@ def iter_spotify_playlist(
print(
f"ERROR: Spotify track seems to be malformed. Track: {src_track!r}"
)
raise (e)
raise e
src_track_name = src_track["track"]["name"]

yield SongInfo(src_track_name, src_track_artist, src_album_name)


def get_playlist_id_by_name(yt: YTMusic, title: str) -> Optional[str]:
"""Look up a YTMusic playlist ID by name. Return None if not found."""
"""Look up a YTMusic playlist ID by name.
Args:
`yt` (YTMusic): _description_
`title` (str): _description_
Returns:
Optional[str]: The playlist ID or None if not found.
"""
for pl in yt.get_library_playlists(limit=5000):
if pl["title"] == title:
return pl["playlistId"]
Expand All @@ -161,14 +181,14 @@ def lookup_song(
The idea is that finding the album and artist and then looking for the exact track
match will be more likely to be accurate than searching for the song and artist and
relying on the YTMusic yt_search_algorithm to figure things out, especially for short tracks
that might be have many contradictory hits like "Survival by Yes".
that might have many contradictory hits like "Survival by Yes".
Args:
yt (YTMusic)
track_name (str): The name of the researched track
artist_name (str): The name of the researched track's artist
album_name (str): The name of the researched track's album
yt_search_algo (int): 0 for exact matching, 1 for extended matching (search past 1st result), 2 for approximate matching (search in videos)
`yt` (YTMusic)
`track_name` (str): The name of the researched track
`artist_name` (str): The name of the researched track's artist
`album_name` (str): The name of the researched track's album
`yt_search_algo` (int): 0 for exact matching, 1 for extended matching (search past 1st result), 2 for approximate matching (search in videos)
Raises:
ValueError: If no track is found, it returns an error
Expand Down Expand Up @@ -214,7 +234,7 @@ def lookup_song(
for song in songs:
# Remove everything in brackets in the song title
song_title_without_brackets = re.sub(
r"[\[\(].*?[\]\)]", "", song["title"]
r"[\[(].*?[])]", "", song["title"]
)
if (
(
Expand Down Expand Up @@ -256,7 +276,7 @@ def lookup_song(
print("Found a video")
return new_song
else:
# Basically we only get here if the song isnt present anywhere on youtube
# Basically we only get here if the song isn't present anywhere on YouTube
raise ValueError(
f"Did not find {track_name} by {artist_name} from {album_name}"
)
Expand Down Expand Up @@ -361,22 +381,32 @@ def copy_playlist(
@@@
"""
yt = get_ytmusic()
pl_name: str = ""

if ytmusic_playlist_id.startswith("+"):
pl_name = ytmusic_playlist_id[1:]

ytmusic_playlist_id = get_playlist_id_by_name(yt, pl_name)
print(f"Looking up playlist '{pl_name}': id={ytmusic_playlist_id}")
if ytmusic_playlist_id is None:
ytmusic_playlist_id = _ytmusic_create_playlist(
yt, title=pl_name, description=pl_name
)
time.sleep(1) # seems to be needed to avoid missing playlist ID error

if ytmusic_playlist_id == "":
if pl_name == "":
print("No playlist name or ID provided, creating playlist...")
spotify_pls: dict = load_playlists_json()
for pl in spotify_pls["playlists"]:
if len(pl.keys()) > 3 and pl["id"] == spotify_playlist_id:
pl_name = pl["name"]

ytmusic_playlist_id = _ytmusic_create_playlist(
yt, title=pl_name, description=pl_name
)
time.sleep(1) # seems to be needed to avoid missing playlist ID error

# create_playlist returns a dict if there was an error
if isinstance(ytmusic_playlist_id, dict):
print(f"ERROR: Failed to create playlist: {ytmusic_playlist_id}")
sys.exit(1)
print(f"NOTE: Created playlist '{pl_name}' with ID: {ytmusic_playlist_id}")
# create_playlist returns a dict if there was an error
if isinstance(ytmusic_playlist_id, dict):
print(f"ERROR: Failed to create playlist: {ytmusic_playlist_id}")
sys.exit(1)
print(f"NOTE: Created playlist '{pl_name}' with ID: {ytmusic_playlist_id}")

copier(
iter_spotify_playlist(
Expand Down
90 changes: 28 additions & 62 deletions spotify2ytmusic/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import cli
import backend
import spotify_backup
from reverse_playlist import reverse_playlist
from typing import Callable


def create_label(parent, text, **kwargs) -> tk.Label:
Expand All @@ -25,7 +25,7 @@ def create_label(parent, text, **kwargs) -> tk.Label:
)


def create_button(parent, text, **kwargs):
def create_button(parent, text, **kwargs) -> tk.Button:
return tk.Button(
parent,
text=text,
Expand Down Expand Up @@ -116,7 +116,7 @@ def __init__(self) -> None:

create_label(self.tab1, text="First, you need to backup your spotify playlists").pack(anchor=tk.CENTER,
expand=True)
create_button(self.tab1, text="Backup", command=lambda: self.call_func(spotify_backup.main, self.tab3)).pack(
create_button(self.tab1, text="Backup", command=lambda: self.call_func(func=spotify_backup.main, args=(), next_tab=self.tab3)).pack(
anchor=tk.CENTER, expand=True)

# # tab2
Expand All @@ -133,8 +133,9 @@ def __init__(self) -> None:

# tab3
create_label(self.tab3, text="Now, you can load your liked songs.").pack(anchor=tk.CENTER, expand=True)
create_button(self.tab3, text="Load", command=self.call_load_liked_songs).pack(
anchor=tk.CENTER, expand=True)
create_button(self.tab3, text="Load", command=lambda x: self.call_func(
func=backend.copier, args=(backend.iter_spotify_playlist(), None, False, 0.1, self.var_algo.get()), next_tab=self.tab4)
).pack(anchor=tk.CENTER, expand=True)

# tab4
create_label(
Expand All @@ -143,7 +144,7 @@ def __init__(self) -> None:
create_button(
self.tab4,
text="List",
command=lambda: self.call_func(cli.list_playlists, self.tab5),
command=lambda: self.call_func(func=cli.list_playlists, args=(), next_tab=self.tab5),
).pack(anchor=tk.CENTER, expand=True)

# tab5
Expand All @@ -155,7 +156,7 @@ def __init__(self) -> None:
create_button(
self.tab5,
text="Copy",
command=lambda: self.call_func(backend.copy_all_playlists, self.tab6),
command=lambda: self.call_func(func=backend.copy_all_playlists, args=(0.1, False, "utf-8", self.var_algo.get()), next_tab=self.tab6),
).pack(anchor=tk.CENTER, expand=True)

# tab6
Expand All @@ -173,9 +174,9 @@ def __init__(self) -> None:
)
self.yt_playlist_id = tk.Entry(self.tab6)
self.yt_playlist_id.pack(anchor=tk.CENTER, expand=True)
create_button(self.tab6, text="Copy", command=self.call_copy_playlist).pack(
anchor=tk.CENTER, expand=True
)
create_button(self.tab6, text="Copy", command=lambda: self.call_func(
func=backend.copy_playlist, args=(self.spotify_playlist_id.get(), self.yt_playlist_id.get()), next_tab=self.tab6)
).pack(anchor=tk.CENTER, expand=True)

# tab7
self.var_scroll = tk.BooleanVar()
Expand Down Expand Up @@ -222,64 +223,31 @@ def redirector(self, input_str="") -> None:
self.logs.config(state=tk.DISABLED)
if self.var_scroll.get():
self.logs.see(tk.END)

def call_load_liked_songs(self):
th = threading.Thread(target=backend.copier, args=(backend.iter_spotify_playlist(), None, False, 0.1, self.var_algo.get()))
th.start()
while th.is_alive():
self.root.update()

self.tabControl.select(self.tab4)
print()

def call_func(self, func, next_tab):
th = threading.Thread(target=func)
def call_func(self, func: Callable, args: tuple, next_tab: ttk.Frame) -> None:
th = threading.Thread(target=func, args=args)
th.start()
while th.is_alive():
self.root.update()
self.tabControl.select(next_tab)
print()

def call_copy_playlist(self):
spotify_playlist_id = self.spotify_playlist_id.get()
yt_playlist_id = self.yt_playlist_id.get()

print()

if spotify_playlist_id == "":
print("Please enter the Spotify playlist ID")
return
if yt_playlist_id == "":
print(
"No Youtube playlist ID, creating one and naming it by the source playlist ID."
)
backend.create_playlist(spotify_playlist_id)
th = threading.Thread(
target=backend.copy_playlist, args=(spotify_playlist_id, yt_playlist_id)
)
th.start()
while th.is_alive():
self.root.update()

self.tabControl.select(self.tab6)
print()
# def call_reverse(self):
# result = [0] # Shared data structure

def call_reverse(self):
result = [0] # Shared data structure
# def target():
# result[0] = reverse_playlist(
# replace=True
# ) # Call the function with specific arguments

def target():
result[0] = reverse_playlist(
replace=True
) # Call the function with specific arguments
# th = threading.Thread(target=target)
# th.start()
# while th.is_alive():
# self.root.update()

th = threading.Thread(target=target)
th.start()
while th.is_alive():
self.root.update()

if result[0] == 0: # Access the return value
self.tabControl.select(self.tab3)
print()
# if result[0] == 0: # Access the return value
# self.tabControl.select(self.tab3)
# print()

def yt_login(self, auto=False) -> None:
def run_in_thread():
Expand Down Expand Up @@ -324,7 +292,7 @@ def load_write_settings(self, action: int) -> None:

exist = True
if action == 0:
with open("settings.json", "a+") as f:
with open("settings.json", "a+"):
pass
with open("settings.json", "r+") as f:
value = f.read()
Expand All @@ -337,9 +305,7 @@ def load_write_settings(self, action: int) -> None:
self.var_algo.set(settings["algo_number"])
else:
with open("settings.json", "w+") as f:
settings = {}
settings["auto_scroll"] = self.var_scroll.get()
settings["algo_number"] = self.var_algo.get()
settings = {"auto_scroll": self.var_scroll.get(), "algo_number": self.var_algo.get()}
json.dump(settings, f)

self.algo_label.config(text=f"Algorithm: {texts[self.var_algo.get()]}")
Expand Down

0 comments on commit 200097d

Please sign in to comment.