Skip to content

Commit

Permalink
rewritten most of the song list class
Browse files Browse the repository at this point in the history
  • Loading branch information
xnetcat committed Jan 23, 2023
1 parent 09a0bb5 commit c3b1dad
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 530 deletions.
4 changes: 3 additions & 1 deletion spotdl/download/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,9 @@ def search_and_download(self, song: Song) -> Tuple[Song, Optional[Path]]:
# and that the song object is not a placeholder
# If it's None extract the current metadata
# And reinitialize the song object
if song.name is None and song.url:
# Force song reinitialization if we are fetching albums
# they have most metadata but not all
if (song.name is None and song.url) or self.settings["fetch_albums"]:
song = reinit_song(song, self.settings["playlist_numbering"])

# Initalize the progress tracker
Expand Down
109 changes: 52 additions & 57 deletions spotdl/types/album.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"""

from dataclasses import dataclass
from typing import Any, Dict, List
from typing import Any, Dict, List, Tuple

from spotdl.types.song import SongList
from spotdl.types.song import Song, SongList
from spotdl.utils.spotify import SpotifyClient

__all__ = ["Album", "AlbumError"]
Expand All @@ -25,43 +25,32 @@ class Album(SongList):

artist: Dict[str, Any]

@classmethod
def search(cls, search_term: str):
"""
Searches for Album from a search term.
### Arguments
- search_term: The search term to use.
### Returns
- The raw search results
"""

spotify_client = SpotifyClient()
raw_search_results = spotify_client.search(search_term, type="album")

if (
raw_search_results is None
or len(raw_search_results.get("albums", {}).get("items", [])) == 0
):
raise AlbumError("No album matches found on spotify")

return raw_search_results

@staticmethod
def get_urls(url: str) -> List[str]:
def get_metadata(url: str) -> Tuple[Dict[str, Any], List[Song]]:
"""
Get urls for all songs in album.
Get metadata for album.
### Arguments
- url: The URL of the album.
### Returns
- A list of urls.
- A dictionary with metadata.
"""

spotify_client = SpotifyClient()

album_metadata = spotify_client.album(url)
if album_metadata is None:
raise AlbumError(
"Couldn't get metadata, check if you have passed correct album id"
)

metadata = {
"name": album_metadata["name"],
"artist": album_metadata["artists"][0],
"url": url,
}

album_response = spotify_client.album_tracks(url)
if album_response is None:
raise AlbumError(
Expand All @@ -83,34 +72,40 @@ def get_urls(url: str) -> List[str]:
if album_response is None:
raise AlbumError(f"Failed to get album response: {url}")

return [
track["external_urls"]["spotify"]
for track in tracks
if track and track.get("id")
]

@staticmethod
def get_metadata(url: str) -> Dict[str, Any]:
"""
Get metadata for album.
### Arguments
- url: The URL of the album.
### Returns
- A dictionary with metadata.
"""

spotify_client = SpotifyClient()

album_metadata = spotify_client.album(url)
if album_metadata is None:
raise AlbumError(
"Couldn't get metadata, check if you have passed correct album id"
songs = []
for track in tracks:
if track["is_local"]:
continue

release_date = album_metadata["release_date"]
artists = artists = [artist["name"] for artist in track["artists"]]

song = Song.from_missing_data(
name=track["name"],
artists=artists,
artist=artists[0],
album_id=album_metadata["id"],
album_name=album_metadata["name"],
album_artist=album_metadata["artists"][0]["name"],
disc_number=track["disc_number"],
disc_count=int(album_metadata["tracks"]["items"][-1]["disc_number"]),
duration=track["duration_ms"],
year=release_date[:4],
date=release_date,
track_number=track["track_number"],
tracks_count=album_metadata["total_tracks"],
song_id=track["id"],
explicit=track["explicit"],
publisher=album_metadata["label"],
url=track["external_urls"]["spotify"],
cover_url=max(
album_metadata["images"], key=lambda i: i["width"] * i["height"]
),
copyright_text=album_metadata["copyrights"][0]["text"]
if album_metadata["copyrights"]
else None,
)

return {
"name": album_metadata["name"],
"artist": album_metadata["artists"][0],
"url": url,
}
songs.append(song)

return metadata, songs
174 changes: 32 additions & 142 deletions spotdl/types/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from dataclasses import dataclass
from typing import Any, Dict, List, Set
from typing import Any, Dict, List, Set, Tuple

from spotdl.types.album import Album
from spotdl.types.song import Song, SongList
Expand All @@ -30,130 +30,29 @@ class Artist(SongList):
genres: List[str]
albums: List[Album]

@classmethod
def from_url(cls, url: str) -> "Artist":
@staticmethod
def get_metadata(url: str) -> Tuple[Dict[str, Any], List[Song]]:
"""
Creates an Artist object from a URL.
Get metadata for artist.
### Arguments
- url: The URL of the artist.
### Returns
- The Artist object.
- Dict with metadata for artist.
"""

if "open.spotify.com" not in url or "artist" not in url:
raise ArtistError(f"Invalid URL: {url}")

metadata = Artist.get_metadata(url)
album_urls = cls.get_albums(url)
# query spotify for artist details
spotify_client = SpotifyClient()

tracks: List[Song] = []
albums: List[Album] = []
# get artist info
raw_artist_meta = spotify_client.artist(url)

# get artist tracks
# same as above, but for tracks
known_tracks: Set[str] = set()
if len(album_urls) < 1:
if raw_artist_meta is None:
raise ArtistError(
"Couldn't get albums, check if you have passed correct artist id"
"Couldn't get metadata, check if you have passed correct artist id"
)

# get all tracks from all albums
# ignore duplicates
urls = []
for album_url in album_urls:
album = Album.from_url(album_url)
albums.append(album)
for track in album.songs:
track_name = slugify(track.name) # type: ignore
if track_name not in known_tracks:
tracks.append(track)
urls.append(track.url)
known_tracks.add(track_name)

return cls(
**metadata,
songs=tracks,
albums=albums,
urls=urls,
)

@classmethod
def search(cls, search_term: str):
"""
Searches for Artist from a search term.
### Arguments
- search_term: The search term to use.
### Returns
- The raw search results
"""

spotify_client = SpotifyClient()
raw_search_results = spotify_client.search(search_term, type="artist")

if (
raw_search_results is None
or len(raw_search_results.get("artists", {}).get("items", [])) == 0
):
raise ArtistError("No artist matches found on spotify")

return raw_search_results

@staticmethod
def get_urls(url: str) -> List[str]:
"""
Get urls for all songs for artist.
### Arguments
- url: The URL of the artist.
### Returns
- List of urls for all songs for artist.
"""

albums = Artist.get_albums(url)

urls = []
for album in albums:
urls.extend(Album.get_urls(album))

return urls

@classmethod
def create_basic_list(cls, url: str) -> "Artist":
"""
Create a basic list with only the required metadata and urls.
### Arguments
- url: The url of the list.
### Returns
- The SongList object.
"""

metadata = Artist.get_metadata(url)
urls = Artist.get_urls(url)

return cls(**metadata, urls=urls, songs=[], albums=[])

@staticmethod
def get_albums(url: str) -> List[str]:
"""
Returns a list with album urls.
### Arguments
- url: The URL of the artist.
### Returns
- List of album urls.
"""

# query spotify for artist details
spotify_client = SpotifyClient()

artist_albums = spotify_client.artist_albums(url, album_type="album,single")

# check if there is response
Expand All @@ -165,10 +64,10 @@ def get_albums(url: str) -> List[str]:
# get artist albums and remove duplicates
# duplicates can occur if the artist has the same album available in
# different countries
albums: Set[str] = set()
albums: List[str] = []
known_albums: Set[str] = set()
for album in artist_albums["items"]:
albums.add(album["external_urls"]["spotify"])
albums.append(album["external_urls"]["spotify"])
known_albums.add(slugify(album["name"]))

# Fetch all artist albums
Expand All @@ -181,37 +80,28 @@ def get_albums(url: str) -> List[str]:
album_name = slugify(album["name"])

if album_name not in known_albums:
albums.add(album["external_urls"]["spotify"])

albums.append(album["external_urls"]["spotify"])
known_albums.add(album_name)

return list(albums)

@staticmethod
def get_metadata(url: str) -> Dict[str, Any]:
"""
Get metadata for artist.
### Arguments
- url: The URL of the artist.
### Returns
- Dict with metadata for artist.
"""

# query spotify for artist details
spotify_client = SpotifyClient()

# get artist info
raw_artist_meta = spotify_client.artist(url)

if raw_artist_meta is None:
raise ArtistError(
"Couldn't get metadata, check if you have passed correct artist id"
)

return {
songs = []
for album in albums:
album_obj = Album.from_url(album, fetch_songs=False)
songs.extend(album_obj.songs)

# Very aggressive deduplication
songs_list = []
songs_names = set()
for song in songs:
slug_name = slugify(song.name)
if song.name not in songs_names:
songs_list.append(song)
songs_names.add(slug_name)

metadata = {
"name": raw_artist_meta["name"],
"genres": raw_artist_meta["genres"],
"url": url,
"albums": albums,
}

return metadata, songs_list
Loading

0 comments on commit c3b1dad

Please sign in to comment.