"""
Defines an artist from ``LyricWiki`` server.
Extract albums and songs from ``https://lyrics.fandom.com/Artist_Name`` page.
Examples::
>>> # Note that names are not case sensible
>>> artist = Artist('daughter')
>>> artist
Artist: Daughter
>>> # Get all albums (compilation, covers etc. included)
>>> artist.get_albums()
[Daughter: EP "His Young Heart" (2011), Songs: 4,
Daughter: EP "The Wild Youth" (2011), Songs: 4,
Daughter: Album "If You Leave" (2013), Songs: 12,
Daughter: Album "Not To Disappear" (2016), Songs: 11,
Daughter: Album "Music From Before The Storm" (2017), Songs: 13,
Daughter: "Songs On Compilations", Songs: 2,
Daughter: Single "Other Songs", Songs: 1]
>>> # Only look for albums / singles released by the artist
>>> artist.get_albums(cover=False, other=False)
[Daughter: EP "His Young Heart" (2011), Songs: 4,
Daughter: EP "The Wild Youth" (2011), Songs: 4,
Daughter: Album "If You Leave" (2013), Songs: 12,
Daughter: Album "Not To Disappear" (2016), Songs: 11,
Daughter: Album "Music From Before The Storm" (2017), Songs: 13,
Daughter: Single "Other Songs", Songs: 1]
>>> # Idem for get_songs()
>>> # Look for an album / song from the artist
>>> song = artist.search_song('candles')
>>> lyrics = song.get_lyrics()
>>> print(lyrics)
That boy, take me away, into the night
Out of the hum of the street lights and into a forest
I'll do whatever you say to me in the dark
Scared I'll be torn apart by a wolf in mask of a familiar name on a birthday card
Blow out all the candles, blow out all the candles
"You're too old to be so shy," he says to me so I stay the night
Just a young heart confusing my mind, but we're both in silence
Wide-eyed, both in silence
Wide-eyed, like we're in a crime scene
etc. ...
>>> # Retrieve the artist from a song / album object
>>> song.get_artist()
Artist: Daughter
>>> # Get additional information from the artist
>>> artist.get_info()
{'Years Active': '2010 - present',
'Band Members': ['Elena Tonra', 'Igor Haefeli', 'Remi Aguilella'],
'Genres': ['Indie Folk', 'Folk Rock'],
'Record Labels': ['4AD']}
>>> # Get merchandise links
>>> artist.get_links()
{'Amazon': ['https://www.amazon.com/exec/obidos/redirect?link_code=ur2&tag=wikia-20&camp=1789&creative=9325&path=https%3A%2F%2Fwww.amazon.com%2F-%2Fe%2FB001LHN42M'],
'iTunes': ['https://itunes.apple.com/us/artist/469701923'],
'AllMusic': ['https://www.allmusic.com/artist/mn0003013627'],
'Discogs': ['http://www.discogs.com/artist/2218596'],
'MusicBrainz': ['https://musicbrainz.org/artist/a1ced3e5-476c-4046-bd74-d428f419989b'],
'Spotify': ['https://open.spotify.com/artist/46CitWgnWrvF9t70C2p1Me'],
'Bandcamp': ['https://ohdaughter.bandcamp.com/']}
>>> # Convert the data to JSON
>>> data = artist.to_json(encode='ascii', nested=False)
These are the most common functions, but others can be used to modify the data.
"""
import warnings
import urllib.parse
from lyricsfandom.utils import *
from lyricsfandom import scrape
from lyricsfandom.meta import ArtistMeta
import lyricsfandom.music as wiki
[docs]class Artist(ArtistMeta):
"""Defines an Artist / Band from ``https://lyrics.fandom.com/wiki/``.
* :attr:`artist_name`: name of the artist.
* :attr:`base`: base page of Lyric Wiki.
* :attr:`href`: href link of the artist.
* :attr:`url`: url page of the artist.
"""
def __init__(self, artist_name):
super().__init__(artist_name)
self._info = None
[docs] def get_info(self):
"""Retrieve additional information of an Artist (like band members, labels, genres etc.).
Returns:
dict
Examples::
>>> artist = Artist('Daughter')
>>> artist.get_info()
{'Years Active': '2010 - present',
'Band Members': ['Elena Tonra', 'Igor Haefeli', 'Remi Aguilella'],
'Genres': ['Indie Folk', 'Folk Rock'],
'Record Labels': ['4AD']}
"""
if not self._info and self.href:
info = {}
soup = self.connect()
if soup:
info = scrape.get_artist_info(soup)
self._info = info
return self._info
def set_info(self, value):
self._info = value
# TODO: deprecate this
[docs] @classmethod
def from_url(cls, url):
"""Construct an Artist from an url.
Args:
url (string): url.
Returns:
Artist
Examples::
>>> artist = Artist.from_url('https://lyrics.fandom.com/wiki/Daughter')
>>> artist
Artist: Daughter
"""
# Return nothing if the connection is incorrect
pass
[docs] def items(self, cover=True, other=True):
"""Connect to ``LyricWiki`` server and scrape albums / songs.
Keywords arguments can be provided to scrape only from released albums, and reject covers, remix, compilation etc.
Args:
cover (bool): if ``True`` scrape featuring or covers songs.
other (bool): if ``True`` scrape remixes or compilation albums.
Returns:
yield Album
"""
self._items = []
# Return nothing if the connection is incorrect
soup = self.connect()
if not soup:
return None
# Scrape songs on different tags.
# 'ol' --> released albums
# 'ul' --> compilation
li_tag = ['ol', 'ul'] if other else 'ol'
for album_span in scrape.scrape_albums(soup):
album_title = album_span.text.strip()
album_name, album_year = parse_album_header(album_title)
album = wiki.Album(self.artist_name,
album_name,
album_year=album_year)
album.register_artist(self)
album_h2 = album_span.parent
# Count the number of songs. Will be used to avoid returning empty albums.
num_songs = 0
for song_a in scrape.scrape_songs(album_h2, li_tag=li_tag):
song_title = song_a.get('title').strip()
# Check if the song was made by the artist or is a featuring / cover
artist_name_song, song_name = parse_song_title(song_title, artist_name=self.artist_name)
if not cover and self.artist_name.lower() not in artist_name_song.lower():
continue
song = wiki.Song(artist_name_song,
song_name,
album_name=album_name,
album_year=album_year)
song.href = song_a.get('href')
album.add_song(song)
num_songs += 1
# Yield only albums that contain songs
if num_songs > 0:
if song_a.parent.parent.parent.name == 'ol':
album.set_album_type()
self.add_album(album)
yield album
[docs] def add_album(self, album, force=None):
"""Add an album to the artist.
When adding a new argument, the album artist's name can be changed to match the parent artist,
using ``force=True``.
If the provided album is the name of an album, it will automatically create an (empty) album and
add it to the artist.
Args:
album (Album or string): album (or album name) to add to the current artist.
force (bool): if ``True``, change the album's ``artist_name`` attribute to match the artist's name.
Examples::
>>> artist = Artist('daughter')
>>> album = Album('daugghter', 'the wild youth')
>>> artist.add_album(album)
>>> artist.get_albums()
[Daugghter: "The Wild Youth", Songs: 0]
>>> artist = Artist('daughter')
>>> album = Album('daugghter', 'the wild youth')
>>> artist.add_album(album, force=True)
>>> artist.get_albums()
[Daughter: "The Wild Youth", Songs: 0]
"""
if isinstance(album, wiki.Album):
if name_to_wiki_id(album.artist_name) != name_to_wiki_id(self.artist_name):
if force is None:
warn_msg = f'\nInvalid Name: Artist name from "{album.artist_name}" does not match ' \
f'parent artist {self.artist_name}. The album has been added, but you can change ' \
f'this behavior by setting the parameter `force=True` to update ' \
f'album\'s information to its parent.'
warnings.warn(warn_msg, RuntimeWarning)
elif force:
album.artist_name = self.artist_name
album.register_artist(self)
self._items.append(album)
elif isinstance(album, str):
new_album = wiki.Album(self.artist_name, album)
new_album.register_artist(self)
self._items.append(new_album)
[docs] def albums(self, **kwargs):
"""Iterate through all Albums made by the artist.
Returns:
yield ALbum
"""
if len(self._items) >= 1:
yield from self._items
else:
yield from self.items(**kwargs)
[docs] def songs(self, **kwargs):
"""Iterate through all songs made by the artist.
Returns:
yield Song
"""
for album in self.albums(**kwargs):
for song in album.songs():
yield song
[docs] def get_albums(self, cover=False, other=False):
"""Get a list of all albums made by the artist.
Keywords arguments can be provided to scrape only from released albums, and reject covers, remix, compilation etc.
Args:
cover (bool): if ``True`` scrape featuring or covers songs.
other (bool): if ``True`` scrape remixes or compilation albums.
Returns:
list
"""
albums = []
for album in self.albums(cover=cover, other=other):
albums.append(album)
return albums
[docs] def get_songs(self, cover=False, other=False):
"""Get a list of all songs made by the artist.
Keywords arguments can be provided to scrape only from songs made by the artist, and reject covers etc.
Args:
cover (bool): if ``True`` scrape featuring or covers songs.
other (bool): if ``True`` scrape remixes or compilation albums.
Returns:
list
"""
songs = []
for song in self.songs():
songs.append(song)
return songs
[docs] def search_album(self, album_name):
"""Search an album from an artist's discography.
Args:
album_name (string): name of the album to look for.
Returns:
Album
"""
for album in self.albums():
if name_to_wiki_id(album_name) == name_to_wiki_id(album.album_name):
return album
# WARNING: no album found
warn_msg = f'\nNot Found: No albums named "{album_name}" found in "{artist.artist_name}" discography. ' \
f'`None` was returned.'
warnings.warn(warn_msg, RuntimeWarning)
return None
[docs] def search_song(self, song_name):
"""Search a song from an artist's playlist.
Args:
song_name (string): name of the song to look for
Returns:
Song
"""
for album in self.albums():
for song in album:
if name_to_wiki_id(song_name) == name_to_wiki_id(song.song_name):
return song
# WARNING: no song found
warn_msg = f'\nNot Found: No songs named "{song_name}" found in "{self.artist_name}" discography. ' \
f'`None` was returned.'
warnings.warn(warn_msg, RuntimeWarning)
return None
[docs] def to_json(self, encode=None):
"""Get the discography of an artist.
Returns:
list
"""
data = {
'artist': self.artist_name,
'info': self.get_info(),
'url': self.url,
'links': self.get_links(),
'albums': []
}
for album in self.albums():
data['albums'].append(album.to_json())
if encode is None:
return data
else:
return serialize_dict(data)